Compare commits
No commits in common. "2d72402ca57cc6e59407a51c18b586ab31b8276d" and "ee3f0b9fd47c656d23a6159b91ebaa323679d5e2" have entirely different histories.
2d72402ca5
...
ee3f0b9fd4
@ -1,439 +0,0 @@
|
|||||||
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 (
|
|
||||||
<div ref={containerRef} className="calculator-input-wrapper">
|
|
||||||
{label && (
|
|
||||||
<label htmlFor={id} className="calculator-label">
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
id={id}
|
|
||||||
value={displayValue}
|
|
||||||
readOnly={readOnly}
|
|
||||||
className={className}
|
|
||||||
onClick={() => !readOnly && setShowKeypad(true)}
|
|
||||||
onFocus={() => !readOnly && setShowKeypad(true)}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
tabIndex={readOnly ? -1 : 0}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{showKeypad && !readOnly && (
|
|
||||||
<div className="keypad-container">
|
|
||||||
<div className="keypad-grid">
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
// const newValue = calculatorRef.current.clear()
|
|
||||||
// setDisplayValue(newValue || '0')
|
|
||||||
// onChange(newValue || '0')
|
|
||||||
handleClear()
|
|
||||||
setHasOperation(false)
|
|
||||||
}}
|
|
||||||
className="btn-clear"
|
|
||||||
>
|
|
||||||
AC
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
// const newValue = calculatorRef.current.deleteNumber()
|
|
||||||
// setDisplayValue(newValue || '0')
|
|
||||||
// onChange(newValue || '0')
|
|
||||||
//setHasOperation(!!calculatorRef.current.operation)
|
|
||||||
handleDelete()
|
|
||||||
}}
|
|
||||||
className="btn-delete"
|
|
||||||
>
|
|
||||||
DEL
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleOperation('÷')} className="btn-operator">
|
|
||||||
÷
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* 숫자 버튼 */}
|
|
||||||
{[7, 8, 9, 4, 5, 6, 1, 2, 3].map((num) => (
|
|
||||||
<button key={num} onClick={() => handleNumber(num)} className="btn-number">
|
|
||||||
{num}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<button onClick={() => handleOperation('×')} className="btn-operator">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleOperation('-')} className="btn-operator">
|
|
||||||
-
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleOperation('+')} className="btn-operator">
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* 0 버튼 */}
|
|
||||||
<button onClick={() => handleNumber('0')} className="btn-number btn-zero">
|
|
||||||
0
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
const newValue = calculatorRef.current.appendNumber('.')
|
|
||||||
onChange(newValue)
|
|
||||||
}}
|
|
||||||
className="btn-number"
|
|
||||||
>
|
|
||||||
.
|
|
||||||
</button>
|
|
||||||
{/* = 버튼 */}
|
|
||||||
<button onClick={handleCompute} className="btn-equals">
|
|
||||||
=
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -21,7 +21,6 @@ import { useRoofFn } from '@/hooks/common/useRoofFn'
|
|||||||
import { usePlan } from '@/hooks/usePlan'
|
import { usePlan } from '@/hooks/usePlan'
|
||||||
import { normalizeDecimal, normalizeDigits } from '@/util/input-utils'
|
import { normalizeDecimal, normalizeDigits } from '@/util/input-utils'
|
||||||
import { logger } from '@/util/logger'
|
import { logger } from '@/util/logger'
|
||||||
import { CalculatorInput } from '@/components/common/input/CalcInput'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 지붕 레이아웃
|
* 지붕 레이아웃
|
||||||
@ -327,11 +326,11 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div className="input-grid mr5">
|
<div className="input-grid mr5">
|
||||||
{/* <input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
readOnly={currentRoof?.roofAngleSet !== item.value}
|
readOnly={currentRoof?.roofAngleSet !== item.value}
|
||||||
value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'}
|
value={index === 0 ? (currentRoof?.pitch || '0') : (currentRoof?.angle || '0')}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const v = normalizeDecimal(e.target.value)
|
const v = normalizeDecimal(e.target.value)
|
||||||
e.target.value = v
|
e.target.value = v
|
||||||
@ -343,34 +342,6 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : getChonByDegree(num), angle: num === '' ? '' : num })
|
setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : getChonByDegree(num), angle: num === '' ? '' : num })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/> */}
|
|
||||||
<CalculatorInput
|
|
||||||
id=""
|
|
||||||
label=""
|
|
||||||
className="input-origin block"
|
|
||||||
readOnly={currentRoof?.roofAngleSet !== item.value}
|
|
||||||
value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'}
|
|
||||||
onChange={(value) => {
|
|
||||||
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),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="thin">{index === 0 ? '寸' : '度'}</span>
|
<span className="thin">{index === 0 ? '寸' : '度'}</span>
|
||||||
@ -449,8 +420,10 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
|||||||
<div className="select-wrap" style={{ width: '160px' }}>
|
<div className="select-wrap" style={{ width: '160px' }}>
|
||||||
<QSelectBox
|
<QSelectBox
|
||||||
options={raftCodes}
|
options={raftCodes}
|
||||||
title={raftCodes?.find((r) => r.clCode === (currentRoof.raft ?? currentRoof?.raftBaseCd))?.clCodeNm}
|
title={
|
||||||
value={currentRoof?.raft ?? currentRoof?.raftBaseCd}
|
raftCodes?.find((r) => r.clCode === ( currentRoof.raft?? currentRoof?.raftBaseCd))?.clCodeNm
|
||||||
|
}
|
||||||
|
value={currentRoof?.raft??currentRoof?.raftBaseCd}
|
||||||
onChange={(e) => handleRafterChange(e.clCode)}
|
onChange={(e) => handleRafterChange(e.clCode)}
|
||||||
sourceKey="clCode"
|
sourceKey="clCode"
|
||||||
targetKey={currentRoof?.raft ? 'raft' : 'raftBaseCd'}
|
targetKey={currentRoof?.raft ? 'raft' : 'raftBaseCd'}
|
||||||
|
|||||||
@ -1,155 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user