From 0c8374b43d9e4869e0f224882a42bd66b8fd7733 Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 29 Aug 2025 15:48:44 +0900 Subject: [PATCH 1/6] =?UTF-8?q?input=20=EA=B3=84=EC=82=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=84=EA=B0=81=3D>?= =?UTF-8?q?=EB=B0=98=EA=B0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 439 ++++++++++++++++++ .../placementShape/PlacementShapeSetting.jsx | 39 +- src/styles/calc.scss | 155 +++++++ src/util/calc-utils.js | 172 +++++++ 4 files changed, 799 insertions(+), 6 deletions(-) create mode 100644 src/components/common/input/CalcInput.jsx create mode 100644 src/styles/calc.scss create mode 100644 src/util/calc-utils.js diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx new file mode 100644 index 00000000..b85a9393 --- /dev/null +++ b/src/components/common/input/CalcInput.jsx @@ -0,0 +1,439 @@ +import React, { useState, useRef, useEffect } from 'react' +import { createCalculator } from '@/util/calc-utils' +import '@/styles/calc.scss' + +export const CalculatorInput = ({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false }) => { + const [showKeypad, setShowKeypad] = useState(false) + const calculatorRef = useRef(createCalculator(options)) + const containerRef = useRef(null) + const inputRef = useRef(null) // input 요소에 대한 ref 추가 + + // 클릭 외부 감지 + useEffect(() => { + const handleClickOutside = (event) => { + if (containerRef.current && !containerRef.current.contains(event.target)) { + setShowKeypad(false) + if (/[+\-×÷]/.test(value)) { + const newValue = calculatorRef.current.clear() + onChange(newValue) + } + } + } + + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, [value, onChange]) + + // 계산기 상태 관리를 위한 state 추가 + const [displayValue, setDisplayValue] = useState(value || '0') + const [hasOperation, setHasOperation] = useState(false) + + // 숫자 입력 처리 함수 수정 + const handleNumber = (num) => { + const calculator = calculatorRef.current + let newValue = '' + + if (hasOperation) { + // 연산자 이후 숫자 입력 시 + if (calculator.currentOperand === '0' || calculator.shouldResetDisplay) { + calculator.currentOperand = num.toString() + calculator.shouldResetDisplay = false + } else { + calculator.currentOperand = (calculator.currentOperand || '') + num + } + newValue = calculator.previousOperand + calculator.operation + calculator.currentOperand + setDisplayValue(newValue) + onChange(calculator.currentOperand) + } else { + // 첫 번째 숫자 입력 시 + if (value === '0' || calculator.shouldResetDisplay) { + calculator.currentOperand = num.toString() + calculator.shouldResetDisplay = false + } else { + calculator.currentOperand = (calculator.currentOperand || '') + num + } + newValue = calculator.currentOperand + setDisplayValue(newValue) + onChange(newValue) + } + + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = newValue.length + inputRef.current.setSelectionRange(len, len) + // Ensure focus is maintained + inputRef.current.focus() + } + }) + } + + // 연산자 처리 함수 수정 + const handleOperation = (operation) => { + const calculator = calculatorRef.current + + // 현재 입력된 값이 없으면 이전 값 사용 (연속 연산 시) + if (!calculator.currentOperand && calculator.previousOperand) { + calculator.operation = operation + const newValue = calculator.previousOperand + operation + setDisplayValue(newValue) + setHasOperation(true) + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = newValue.length + inputRef.current.setSelectionRange(len, len) + inputRef.current.focus() + } + }) + return + } + + if (hasOperation) { + // 이미 연산자가 있는 경우, 계산 실행 + const result = calculator.compute() + if (result !== undefined) { + calculator.previousOperand = result.toString() + calculator.operation = operation + calculator.currentOperand = '' + const newValue = result.toString() + operation + setDisplayValue(newValue) + setHasOperation(true) + onChange(result.toString()) + + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = newValue.length + inputRef.current.setSelectionRange(len, len) + inputRef.current.focus() + } + }) + } + } else { + // 새로운 연산자 추가 + calculator.previousOperand = displayValue + calculator.operation = operation + calculator.currentOperand = '' + const newValue = displayValue + operation + setDisplayValue(newValue) + setHasOperation(true) + onChange(displayValue) + + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = newValue.length + inputRef.current.setSelectionRange(len, len) + inputRef.current.focus() + } + }) + } + } + + // AC 버튼 클릭 핸들러 + const handleClear = () => { + const calculator = calculatorRef.current + const newValue = calculator.clear() + const displayValue = newValue || '0' + setDisplayValue(displayValue) + setHasOperation(false) + onChange(displayValue) + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = displayValue.length + inputRef.current.setSelectionRange(len, len) + // Ensure focus is maintained + inputRef.current.focus() + } + }) + } + + // 계산 실행 함수 수정 + const handleCompute = () => { + if (!hasOperation) return + + const calculator = calculatorRef.current + + // 현재 입력된 값이 없으면 0으로 설정 + if (!calculator.currentOperand) { + calculator.currentOperand = '0' + } + + // 계산 실행 + const result = calculator.compute() + + // 계산 결과가 유효한지 확인 + if (result === undefined || result === null) { + console.error('계산 결과가 유효하지 않습니다.') + return + } + + // 상태 업데이트 + const resultStr = result.toString() + setDisplayValue(resultStr) + setHasOperation(false) + onChange(resultStr) + + // 계산기 상태 초기화 (다음 계산을 위해) + calculator.clear() + calculator.previousOperand = resultStr + + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = resultStr.length + inputRef.current.setSelectionRange(len, len) + // Ensure focus is maintained + inputRef.current.focus() + } + }) + } + + // DEL 버튼 클릭 핸들러 + const handleDelete = () => { + const calculator = calculatorRef.current + const newValue = calculator.deleteNumber() + const displayValue = newValue || '0' + setDisplayValue(displayValue) + setHasOperation(!!calculator.operation) + onChange(displayValue) + // 포커스와 커서 위치 설정 + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = displayValue.length + inputRef.current.setSelectionRange(len, len) + // Ensure focus is maintained + inputRef.current.focus() + } + }) + } + + // input의 onChange 이벤트 처리 - 허용된 계산기 입력만 처리 + const handleInputChange = (e) => { + if (readOnly) return + + const inputValue = e.target.value + + // 허용된 문자만 필터링 (숫자, 연산자, 소수점) + const filteredValue = inputValue.replace(/[^0-9+\-×÷.]/g, '') + + // 연산자 연속 입력 방지 + const lastChar = filteredValue[filteredValue.length - 1] + const prevChar = filteredValue[filteredValue.length - 2] + + if (['+', '×', '÷'].includes(lastChar) && ['+', '-', '×', '÷', '.'].includes(prevChar)) { + // 연산자나 소수점이 연속으로 입력된 경우 이전 문자 유지 + return + } + + // 소수점 중복 입력 방지 + const parts = filteredValue.split(/[+\-×÷]/) + if (parts[parts.length - 1].split('.').length > 2) { + // 한 숫자에 소수점이 2개 이상인 경우 + return + } + + setDisplayValue(filteredValue) + + // 계산기 상태 업데이트 + if (filteredValue !== displayValue) { + const calculator = calculatorRef.current + const hasOperation = /[+\-×÷]/.test(filteredValue) + + if (hasOperation) { + const [operand1, operator, operand2] = filteredValue.split(/([+\-×÷])/) + calculator.previousOperand = operand1 || '' + calculator.operation = operator || '' + calculator.currentOperand = operand2 || '' + setHasOperation(true) + } else { + calculator.currentOperand = filteredValue + setHasOperation(false) + } + + onChange(filteredValue) + } + } + + // 키패드 토글 함수 + const toggleKeypad = (e) => { + if (e) { + e.preventDefault() + e.stopPropagation() + } + const newShowKeypad = !showKeypad + setShowKeypad(newShowKeypad) + + // Show keypad 시에만 포커스 유지 + if (newShowKeypad) { + setTimeout(() => { + inputRef.current?.focus() + }, 0) + } + } + + // 키보드 이벤트 처리 수정 + const handleKeyDown = (e) => { + if (readOnly) return + + // Tab 키는 계산기 숨기고 기본 동작 허용 + if (e.key === 'Tab') { + setShowKeypad(false) + return + } + + // 방향키는 기본 동작 허용 + if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { + setShowKeypad(true) + return + } + + e.preventDefault() + const calculator = calculatorRef.current + const { allowDecimal } = options + + if (e.key === '.') { + // allowDecimal이 false이면 소수점 입력 무시 + if (!allowDecimal) return + + // 소수점 입력 처리 + const currentValue = displayValue.toString() + const parts = currentValue.split(/[+\-×÷]/) + const lastPart = parts[parts.length - 1] + + // 이미 소수점이 있으면 무시 + if (!lastPart.includes('.')) { + handleNumber(e.key) + } + } else if (/^[0-9]$/.test(e.key)) { + handleNumber(e.key) + } else { + switch (e.key) { + case '+': + case '-': + case '*': + case '/': + const opMap = { '*': '×', '/': '÷' } + handleOperation(opMap[e.key] || e.key) + break + case 'Enter': + case '=': + handleCompute() + break + case 'Backspace': + case 'Delete': + handleDelete() + break + + case 'Escape': + handleClear() + setShowKeypad(false) + break + + default: + break + } + } + } + + return ( +
+ {label && ( + + )} + !readOnly && setShowKeypad(true)} + onFocus={() => !readOnly && setShowKeypad(true)} + onChange={handleInputChange} + onKeyDown={handleKeyDown} + tabIndex={readOnly ? -1 : 0} + /> + + {showKeypad && !readOnly && ( +
+
+ + + + + {/* 숫자 버튼 */} + {[7, 8, 9, 4, 5, 6, 1, 2, 3].map((num) => ( + + ))} + + + + + + {/* 0 버튼 */} + + + {/* = 버튼 */} + +
+
+ )} +
+ ) +} diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index d6bab594..2d5d9c41 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -21,6 +21,7 @@ import { useRoofFn } from '@/hooks/common/useRoofFn' import { usePlan } from '@/hooks/usePlan' import { normalizeDecimal, normalizeDigits } from '@/util/input-utils' import { logger } from '@/util/logger' +import { CalculatorInput } from '@/components/common/input/CalcInput' /** * 지붕 레이아웃 @@ -326,11 +327,11 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
- { const v = normalizeDecimal(e.target.value) e.target.value = v @@ -342,6 +343,34 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : getChonByDegree(num), angle: num === '' ? '' : num }) } }} + /> */} + { + if (index === 0) { + const num = value === '' ? '' : Number(value) + setCurrentRoof({ + ...currentRoof, + pitch: num === '' ? '' : num, + angle: num === '' ? '' : getDegreeByChon(num), + }) + } else { + const num = value === '' ? '' : Number(value) + setCurrentRoof({ + ...currentRoof, + pitch: num === '' ? '' : getChonByDegree(num), + angle: num === '' ? '' : num, + }) + } + }} + options={{ + allowNegative: false, + allowDecimal: (index !== 0), + }} />
{index === 0 ? '寸' : '度'} @@ -420,10 +449,8 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
r.clCode === ( currentRoof.raft?? currentRoof?.raftBaseCd))?.clCodeNm - } - value={currentRoof?.raft??currentRoof?.raftBaseCd} + title={raftCodes?.find((r) => r.clCode === (currentRoof.raft ?? currentRoof?.raftBaseCd))?.clCodeNm} + value={currentRoof?.raft ?? currentRoof?.raftBaseCd} onChange={(e) => handleRafterChange(e.clCode)} sourceKey="clCode" targetKey={currentRoof?.raft ? 'raft' : 'raftBaseCd'} diff --git a/src/styles/calc.scss b/src/styles/calc.scss new file mode 100644 index 00000000..f30bc691 --- /dev/null +++ b/src/styles/calc.scss @@ -0,0 +1,155 @@ +// Variables +$colors: ( + 'dark-700': #1f2937, + 'dark-600': #334155, + 'dark-500': #4b5563, + 'dark-400': #6b7280, + 'dark-300': #374151, + 'primary': #10b981, + 'primary-dark': #059669, + 'danger': #ef4444, + 'danger-dark': #dc2626, + 'warning': #f59e0b, + 'warning-dark': #d97706, + 'border': #475569 +); + +// Mixins +@mixin button-styles { + padding: 0.125rem; + border-radius: 0.5rem; + font-weight: bold; + font-size: 0.625rem; + color: white; + border: none; + cursor: pointer; + transition: all 0.1s ease-in-out; + + &:active { + transform: scale(0.95); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + } +} + +// Calculator Input Wrapper +.calculator-input-wrapper { + position: relative; + width: 100%; + display: inline-block; + + // Input Field + .calculator-input { + width: 100%; + padding: 0.5rem 1rem; + background-color: map-get($colors, 'dark-600'); + border: 1px solid map-get($colors, 'border'); + color: white; + font-weight: bold; + border-radius: 0.5rem; + text-align: right; + cursor: pointer; + font-size: 0.625rem; + box-sizing: border-box; + + &:focus { + outline: none; + border-color: map-get($colors, 'primary'); + box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); + } + } + + // Keypad Container + .keypad-container { + position: absolute; + top: calc(100% + 2px); + left: 0; + right: 0; + width: 120px; + background-color: map-get($colors, 'dark-700'); + border-radius: 0.5rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + padding: 0.5rem; + z-index: 1000; + animation: fadeIn 0.15s ease-out; + border: 1px solid map-get($colors, 'border'); + box-sizing: border-box; + + // Keypad Grid + .keypad-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.125rem; + + // Button Base + button { + @include button-styles; + } + + // Button Types + .btn-number { + background-color: map-get($colors, 'dark-500'); + + &:hover { + background-color: map-get($colors, 'dark-400'); + } + } + + .btn-operator { + background-color: map-get($colors, 'warning'); + + &:hover { + background-color: map-get($colors, 'warning-dark'); + } + } + + .btn-clear { + background-color: map-get($colors, 'danger'); + grid-column: span 2; + + &:hover { + background-color: map-get($colors, 'danger-dark'); + } + } + + .btn-delete { + background-color: map-get($colors, 'dark-500'); + + &:hover { + background-color: map-get($colors, 'dark-300'); + } + } + + .btn-equals { + background-color: map-get($colors, 'primary'); + + &:hover { + background-color: map-get($colors, 'primary-dark'); + } + } + + .btn-zero { + grid-column: span 2; + } + } + } +} + +// Animations +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +// Responsive Design +@media (max-width: 640px) { + .keypad-grid button { + padding: 0.5rem; + font-size: 0.875rem; + } +} \ No newline at end of file diff --git a/src/util/calc-utils.js b/src/util/calc-utils.js new file mode 100644 index 00000000..49e76721 --- /dev/null +++ b/src/util/calc-utils.js @@ -0,0 +1,172 @@ +export const createCalculator = (options = {}) => { + const state = { + currentOperand: '', + previousOperand: '', + operation: undefined, + shouldResetDisplay: false, + allowNegative: options.allowNegative ?? true, + allowDecimal: options.allowDecimal ?? true, + allowZero: options.allowZero ?? true, + decimalPlaces: options.decimalPlaces ?? null, + } + + // Expose state for debugging and direct access + const getState = () => ({ ...state }) + + const clear = () => { + state.currentOperand = '' + state.previousOperand = '' + state.operation = undefined + state.shouldResetDisplay = false + return state.currentOperand + } + + const deleteNumber = () => { + if (state.currentOperand.length <= 1) { + state.currentOperand = '0' + } else { + state.currentOperand = state.currentOperand.toString().slice(0, -1) + } + return state.currentOperand + } + + const appendNumber = (number) => { + if (number === '.' && !state.allowDecimal) return state.currentOperand + if (number === '.' && state.currentOperand.includes('.')) return state.currentOperand + + if (state.shouldResetDisplay) { + state.currentOperand = number.toString() + state.shouldResetDisplay = false + } else { + if (state.currentOperand === '0' && number !== '.') { + state.currentOperand = number.toString() + } else { + state.currentOperand = state.currentOperand.toString() + number.toString() + } + } + return state.currentOperand + } + + const chooseOperation = (operation) => { + if (operation === '-' && state.currentOperand === '0' && state.previousOperand === '' && state.allowNegative) { + state.currentOperand = '-' + return state.currentOperand + } + + if (state.currentOperand === '' || state.currentOperand === '-') return state.currentOperand + + // If there's a previous operation, compute it first + if (state.previousOperand !== '') { + compute() + } + + state.operation = operation + state.previousOperand = state.currentOperand + state.currentOperand = '' + return state.previousOperand + state.operation + } + + const compute = () => { + // If there's no operation, return the current value + if (!state.operation) return parseFloat(state.currentOperand || '0') + + // If there's no current operand but we have a previous one, use previous as current + if (state.currentOperand === '' && state.previousOperand !== '') { + state.currentOperand = state.previousOperand + } + + const prev = parseFloat(state.previousOperand) + const current = parseFloat(state.currentOperand) + + if (isNaN(prev) || isNaN(current)) return 0 + + let result + switch (state.operation) { + case '+': + result = prev + current + break + case '-': + result = prev - current + break + case '×': + result = prev * current + break + case '÷': + if (current === 0) { + state.currentOperand = 'Error' + return 0 + } + result = prev / current + break + default: + return parseFloat(state.currentOperand || '0') + } + + // Apply formatting and constraints + if (state.decimalPlaces !== null) { + result = Number(result.toFixed(state.decimalPlaces)) + } + + if (!state.allowDecimal) { + result = Math.round(result) + } + + if (!state.allowNegative && result < 0) { + result = 0 + } + + if (!state.allowZero && result === 0) { + result = 1 + } + + // Update state + state.currentOperand = result.toString() + state.previousOperand = '' + state.operation = undefined + state.shouldResetDisplay = true + + return result + } + + // Getter methods for the calculator state + const getCurrentOperand = () => state.currentOperand + const getPreviousOperand = () => state.previousOperand + const getOperation = () => state.operation + const getDisplayValue = () => { + if (state.operation && state.previousOperand) { + return `${state.previousOperand} ${state.operation} ${state.currentOperand || ''}`.trim() + } + return state.currentOperand + } + + return { + // Core calculator methods + clear, + delete: deleteNumber, // Alias for deleteNumber for compatibility + deleteNumber, + appendNumber, + chooseOperation, + compute, + + // State getters + getDisplayValue, + getCurrentOperand, + getPreviousOperand, + getOperation, + + // Direct state access (for debugging) + getState, + + // Direct property access (for compatibility with CalcInput.jsx) + get currentOperand() { return state.currentOperand }, + get previousOperand() { return state.previousOperand }, + get operation() { return state.operation }, + get shouldResetDisplay() { return state.shouldResetDisplay }, + + // Setter for direct property access (if needed) + set currentOperand(value) { state.currentOperand = value }, + set previousOperand(value) { state.previousOperand = value }, + set operation(value) { state.operation = value }, + set shouldResetDisplay(value) { state.shouldResetDisplay = value } + } +} From f15ff10bf6d995ecfe99ec99a4cc116ec6883e01 Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 29 Aug 2025 17:10:05 +0900 Subject: [PATCH 2/6] =?UTF-8?q?input=20=EA=B3=84=EC=82=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 162 +++++++----------- .../placementShape/PlacementShapeSetting.jsx | 14 +- 2 files changed, 70 insertions(+), 106 deletions(-) diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index b85a9393..8e60506b 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -4,34 +4,37 @@ import '@/styles/calc.scss' export const CalculatorInput = ({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false }) => { const [showKeypad, setShowKeypad] = useState(false) + const [displayValue, setDisplayValue] = useState(value || '0') + const [hasOperation, setHasOperation] = useState(false) const calculatorRef = useRef(createCalculator(options)) const containerRef = useRef(null) - const inputRef = useRef(null) // input 요소에 대한 ref 추가 + const inputRef = useRef(null) + + // Sync displayValue with value prop + useEffect(() => { + setDisplayValue(value || '0') + }, [value]) // 클릭 외부 감지 useEffect(() => { const handleClickOutside = (event) => { if (containerRef.current && !containerRef.current.contains(event.target)) { setShowKeypad(false) - if (/[+\-×÷]/.test(value)) { - const newValue = calculatorRef.current.clear() - onChange(newValue) + if (hasOperation) { + // If there's an operation in progress, compute the result when losing focus + handleCompute() } } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) - }, [value, onChange]) - - // 계산기 상태 관리를 위한 state 추가 - const [displayValue, setDisplayValue] = useState(value || '0') - const [hasOperation, setHasOperation] = useState(false) + }, [value, onChange, hasOperation]) // 숫자 입력 처리 함수 수정 const handleNumber = (num) => { const calculator = calculatorRef.current - let newValue = '' + let newDisplayValue = '' if (hasOperation) { // 연산자 이후 숫자 입력 시 @@ -41,30 +44,34 @@ export const CalculatorInput = ({ value, onChange, label, options = {}, id, clas } else { calculator.currentOperand = (calculator.currentOperand || '') + num } - newValue = calculator.previousOperand + calculator.operation + calculator.currentOperand - setDisplayValue(newValue) - onChange(calculator.currentOperand) + newDisplayValue = calculator.previousOperand + calculator.operation + calculator.currentOperand + setDisplayValue(newDisplayValue) } else { // 첫 번째 숫자 입력 시 - if (value === '0' || calculator.shouldResetDisplay) { + if (displayValue === '0' || calculator.shouldResetDisplay) { calculator.currentOperand = num.toString() calculator.shouldResetDisplay = false + newDisplayValue = calculator.currentOperand + setDisplayValue(newDisplayValue) + if (!hasOperation) { + onChange(calculator.currentOperand) + } } else { calculator.currentOperand = (calculator.currentOperand || '') + num + newDisplayValue = calculator.currentOperand + setDisplayValue(newDisplayValue) + if (!hasOperation) { + onChange(newDisplayValue) + } } - newValue = calculator.currentOperand - setDisplayValue(newValue) - onChange(newValue) } - // 포커스와 커서 위치 설정 + // 포커스와 커서 위치 설정 (새로운 값의 길이로 설정) requestAnimationFrame(() => { if (inputRef.current) { inputRef.current.focus() - const len = newValue.length + const len = newDisplayValue.length inputRef.current.setSelectionRange(len, len) - // Ensure focus is maintained - inputRef.current.focus() } }) } @@ -72,67 +79,42 @@ export const CalculatorInput = ({ value, onChange, label, options = {}, id, clas // 연산자 처리 함수 수정 const handleOperation = (operation) => { const calculator = calculatorRef.current + let newDisplayValue = '' // 현재 입력된 값이 없으면 이전 값 사용 (연속 연산 시) if (!calculator.currentOperand && calculator.previousOperand) { calculator.operation = operation - const newValue = calculator.previousOperand + operation - setDisplayValue(newValue) + newDisplayValue = calculator.previousOperand + operation + setDisplayValue(newDisplayValue) setHasOperation(true) - // 포커스와 커서 위치 설정 - requestAnimationFrame(() => { - if (inputRef.current) { - inputRef.current.focus() - const len = newValue.length - inputRef.current.setSelectionRange(len, len) - inputRef.current.focus() - } - }) - return - } - - if (hasOperation) { + } else if (hasOperation) { // 이미 연산자가 있는 경우, 계산 실행 const result = calculator.compute() if (result !== undefined) { calculator.previousOperand = result.toString() calculator.operation = operation calculator.currentOperand = '' - const newValue = result.toString() + operation - setDisplayValue(newValue) - setHasOperation(true) - onChange(result.toString()) - - // 포커스와 커서 위치 설정 - requestAnimationFrame(() => { - if (inputRef.current) { - inputRef.current.focus() - const len = newValue.length - inputRef.current.setSelectionRange(len, len) - inputRef.current.focus() - } - }) + newDisplayValue = calculator.previousOperand + operation + setDisplayValue(newDisplayValue) } } else { - // 새로운 연산자 추가 - calculator.previousOperand = displayValue + // 첫 번째 연산자 입력 시 + calculator.previousOperand = calculator.currentOperand || '0' calculator.operation = operation calculator.currentOperand = '' - const newValue = displayValue + operation - setDisplayValue(newValue) setHasOperation(true) - onChange(displayValue) - - // 포커스와 커서 위치 설정 - requestAnimationFrame(() => { - if (inputRef.current) { - inputRef.current.focus() - const len = newValue.length - inputRef.current.setSelectionRange(len, len) - inputRef.current.focus() - } - }) + newDisplayValue = calculator.previousOperand + operation + setDisplayValue(newDisplayValue) } + + // 포커스와 커서 위치 설정 (새로운 값의 길이로 설정) + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = newDisplayValue.length + inputRef.current.setSelectionRange(len, len) + } + }) } // AC 버튼 클릭 핸들러 @@ -157,44 +139,26 @@ export const CalculatorInput = ({ value, onChange, label, options = {}, id, clas // 계산 실행 함수 수정 const handleCompute = () => { - if (!hasOperation) return - const calculator = calculatorRef.current + if (!hasOperation || !calculator.currentOperand) return - // 현재 입력된 값이 없으면 0으로 설정 - if (!calculator.currentOperand) { - calculator.currentOperand = '0' - } - - // 계산 실행 const result = calculator.compute() - - // 계산 결과가 유효한지 확인 - if (result === undefined || result === null) { - console.error('계산 결과가 유효하지 않습니다.') - return + if (result !== undefined) { + const resultStr = result.toString() + setDisplayValue(resultStr) + setHasOperation(false) + // Only call onChange with the final result + onChange(resultStr) + + // 포커스 유지 및 커서 위치 설정 (맨 뒤로) + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + const len = resultStr.length + inputRef.current.setSelectionRange(len, len) + } + }) } - - // 상태 업데이트 - const resultStr = result.toString() - setDisplayValue(resultStr) - setHasOperation(false) - onChange(resultStr) - - // 계산기 상태 초기화 (다음 계산을 위해) - calculator.clear() - calculator.previousOperand = resultStr - - // 포커스와 커서 위치 설정 - requestAnimationFrame(() => { - if (inputRef.current) { - inputRef.current.focus() - const len = resultStr.length - inputRef.current.setSelectionRange(len, len) - // Ensure focus is maintained - inputRef.current.focus() - } - }) } // DEL 버튼 클릭 핸들러 diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 2d5d9c41..a8ee7102 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -353,23 +353,23 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla onChange={(value) => { if (index === 0) { const num = value === '' ? '' : Number(value) - setCurrentRoof({ - ...currentRoof, + setCurrentRoof(prev => ({ + ...prev, pitch: num === '' ? '' : num, angle: num === '' ? '' : getDegreeByChon(num), - }) + })) } else { const num = value === '' ? '' : Number(value) - setCurrentRoof({ - ...currentRoof, + setCurrentRoof( prev => ({ + ...prev, pitch: num === '' ? '' : getChonByDegree(num), angle: num === '' ? '' : num, - }) + })) } }} options={{ allowNegative: false, - allowDecimal: (index !== 0), + allowDecimal: false //(index !== 0), }} />
From 872c27734bebf4cf7eca9ca68b4fa9b6dc6a51b8 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 2 Sep 2025 14:30:12 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=EB=8F=99=EC=9D=BC=20=EA=B2=BD=EC=82=AC,=20?= =?UTF-8?q?=EB=8F=99=EC=9D=BC=20=EB=B0=A9=EB=A9=B4=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=90=98=EC=96=B4=EC=9E=88=EC=9D=84=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=97=90=EB=A7=8C=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useCirCuitTrestle.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hooks/useCirCuitTrestle.js b/src/hooks/useCirCuitTrestle.js index 12e1b28b..a35d3c8c 100644 --- a/src/hooks/useCirCuitTrestle.js +++ b/src/hooks/useCirCuitTrestle.js @@ -10,7 +10,7 @@ import { selectedModelsState, seriesState, } from '@/store/circuitTrestleAtom' -import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions' +import { selectedModuleState } from '@/store/selectedModuleOptions' import { useContext, useEffect } from 'react' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { useMessage } from './useMessage' @@ -101,7 +101,11 @@ export function useCircuitTrestle(executeEffect = false) { // result 배열에서 roofSurface 값을 기준으로 순서대로 정렬한다. - return groupSort(result) + if (pcsCheck.division) { + return groupSort(result) + } else { + return result + } } const groupSort = (arr) => { From 8f2a78ef1ed2c64996c998189843b945f45fc81e Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 2 Sep 2025 17:25:00 +0900 Subject: [PATCH 4/6] =?UTF-8?q?big.mjs:139=20Uncaught=20Error:=20[big.js]?= =?UTF-8?q?=20Invalid=20number=20=20at=20klass.setLength=20(QLine.js:72:36?= =?UTF-8?q?)=20(NaN=20=3D>=200=20=EC=B2=98=EB=A6=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/fabric/QLine.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 7881712e..77254d5d 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -2,6 +2,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { getDirectionByPoint } from '@/util/canvas-util' import { calcLinePlaneSize } from '@/util/qpolygon-utils' +import { logger } from '@/util/logger' export const QLine = fabric.util.createClass(fabric.Line, { type: 'QLine', @@ -69,7 +70,14 @@ export const QLine = fabric.util.createClass(fabric.Line, { }, setLength() { - this.length = calcLinePlaneSize(this) / 10 + // Ensure all required properties are valid numbers + const { x1, y1, x2, y2 } = this; + if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) { + logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 }); + this.length = 0; + return; + } + this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10; }, addLengthText() { From 662b38aac7fe106372bfb3fa764a73db67fb2327 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 2 Sep 2025 17:26:18 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=20=3D>=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/modal/basic/step/Placement.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index e96cf5c0..091129a8 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -169,6 +169,7 @@ const Placement = forwardRef((props, refs) => {
+ {moduleData.header.map((data) => ( ))} + {selectedModules?.itemList && @@ -216,7 +218,7 @@ const Placement = forwardRef((props, refs) => { className="input-origin block" name="row" value={props.layoutSetup[index]?.row ?? 1} - defaultValue={0} + //defaultValue={0} onChange={(e) => handleLayoutSetup(e, item.itemId, index)} /> @@ -228,7 +230,7 @@ const Placement = forwardRef((props, refs) => { className="input-origin block" name="col" value={props.layoutSetup[index]?.col ?? 1} - defaultValue={0} + //defaultValue={0} onChange={(e) => handleLayoutSetup(e, item.itemId, index)} /> From 6dd66ee27d59d2248210ee74db3fc59ec232e4b8 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 2 Sep 2025 17:28:37 +0900 Subject: [PATCH 6/6] =?UTF-8?q?value,=20defaultValue=20=EB=8F=99=EC=8B=9C?= =?UTF-8?q?=20=EC=A1=B4=EC=9E=AC=20=EC=95=88=EB=90=A8=20=3D>=20defaultValu?= =?UTF-8?q?e=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx b/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx index 7c9d4f51..32364844 100644 --- a/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx +++ b/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx @@ -86,7 +86,7 @@ export default function RoofAllocationSetting(props) { return (
- +
{pitchText}
{data.type === 'check' ? ( @@ -181,6 +182,7 @@ const Placement = forwardRef((props, refs) => { )}