880 lines
37 KiB
JavaScript
880 lines
37 KiB
JavaScript
import { useEffect, useRef, useState } from 'react'
|
|
import Image from 'next/image'
|
|
import { useMessage } from '@/hooks/useMessage'
|
|
import { usePopup } from '@/hooks/usePopup'
|
|
|
|
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
|
|
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
|
|
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
|
|
|
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
|
import { addedRoofsState, roofDisplaySelector, roofMaterialsAtom } from '@/store/settingAtom'
|
|
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
|
import QSelectBox from '@/components/common/select/QSelectBox'
|
|
import { globalLocaleStore } from '@/store/localeAtom'
|
|
|
|
import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
|
|
import { usePolygon } from '@/hooks/usePolygon'
|
|
import { canvasState, canvasSettingState, currentCanvasPlanState, currentMenuState } from '@/store/canvasAtom'
|
|
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
|
import { MENU, POLYGON_TYPE } from '@/common/common'
|
|
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
|
import { usePlan } from '@/hooks/usePlan'
|
|
import { normalizeDecimal } from '@/util/input-utils'
|
|
import { CalculatorInput } from '@/components/common/input/CalcInput'
|
|
import { usePdfImport } from '@/hooks/pdf-import/usePdfImport'
|
|
import { logger } from '@/util/logger'
|
|
// [LOW-PITCH-WARN 2026-05-06] 저구배 + 특정 기와 사용 시 시공 매뉴얼 안내 alert
|
|
import { useSwal } from '@/hooks/useSwal'
|
|
import { notifyLowPitchRestrictionForRoofs, isLowPitchRestricted, LOW_PITCH_RESTRICTED_ROOF_IDS } from '@/util/roof-pitch-warning'
|
|
// [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 import + 호출 함께 제거
|
|
import { debugCapture } from '@/util/debugCapture'
|
|
|
|
const INPUT_MODE = {
|
|
SIZE_ROOF: '1',
|
|
SIZE_ACTUAL: '2',
|
|
PDF: 'pdf',
|
|
}
|
|
|
|
const PDF_MAX_FILE_BYTES = 20 * 1024 * 1024
|
|
|
|
const PDF_PAGE_MODE = {
|
|
ALL: 'all',
|
|
SPECIFY: 'specify',
|
|
}
|
|
|
|
/**
|
|
* 지붕 레이아웃
|
|
*/
|
|
export const ROOF_MATERIAL_LAYOUT = {
|
|
PARALLEL: 'P',
|
|
STAIRS: 'S',
|
|
}
|
|
|
|
export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, planNo, openPoint }) {
|
|
const [showSizeGuideModal, setShowSizeGuidModal] = useState(false)
|
|
const [showMaterialGuideModal, setShowMaterialGuidModal] = useState(false)
|
|
const [useCalcPad, setUseCalcPad] = useState(false)
|
|
const disableKeypad = !useCalcPad
|
|
const { getMessage } = useMessage()
|
|
const roofMaterials = useRecoilValue(roofMaterialsAtom)
|
|
|
|
const globalLocale = useRecoilValue(globalLocaleStore)
|
|
const { basicSetting, setBasicSettings, fetchBasicSettings, basicSettingSave } = useCanvasSetting()
|
|
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState)
|
|
const { findCommonCode } = useCommonCode()
|
|
const [raftCodes, setRaftCodes] = useState([])
|
|
/** 서까래 정보 */
|
|
const [currentRoof, setCurrentRoof] = useState(null)
|
|
/** 현재 선택된 지붕재 정보 */
|
|
const { closePopup } = usePopup()
|
|
/** usePopup에서 closePopup 함수 가져오기 */
|
|
const { drawDirectionArrow } = usePolygon()
|
|
const { setSurfaceShapePattern } = useRoofFn()
|
|
const canvas = useRecoilValue(canvasState)
|
|
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
|
|
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
|
const roofDisplay = useRecoilValue(roofDisplaySelector)
|
|
|
|
const { setPolygonLinesActualSize } = usePolygon()
|
|
const { setSelectedMenu } = useCanvasMenu()
|
|
const setCurrentMenu = useSetRecoilState(currentMenuState)
|
|
|
|
const roofRef = {
|
|
roofCd: useRef(null),
|
|
width: useRef(null),
|
|
length: useRef(null),
|
|
rafter: useRef(null),
|
|
hajebichi: useRef(null),
|
|
}
|
|
const { saveCanvas } = usePlan()
|
|
// [LOW-PITCH-WARN 2026-05-06]
|
|
const { swalFire } = useSwal()
|
|
const { applyOuterline } = usePdfImport()
|
|
|
|
// PDF 업로드 관련 상태
|
|
const [inputMode, setInputMode] = useState(INPUT_MODE.SIZE_ROOF)
|
|
const [pdfFile, setPdfFile] = useState(null)
|
|
const [pdfPageMode, setPdfPageMode] = useState(PDF_PAGE_MODE.ALL)
|
|
const [pdfFacadePages, setPdfFacadePages] = useState('')
|
|
const [pdfFloorPages, setPdfFloorPages] = useState('')
|
|
const [pdfAnalyzing, setPdfAnalyzing] = useState(false)
|
|
const pdfInputRef = useRef(null)
|
|
/**
|
|
* 치수 입력방법(복시도입력/실측값입력/도면 파일 업로드)
|
|
*/
|
|
const roofSizeSetArray = [
|
|
{ id: 'ra01', name: 'roofSizeSet', value: INPUT_MODE.SIZE_ROOF, message: 'modal.placement.initial.setting.size.roof' },
|
|
{ id: 'ra02', name: 'roofSizeSet', value: INPUT_MODE.SIZE_ACTUAL, message: 'modal.placement.initial.setting.size.actual' },
|
|
// { id: 'ra-pdf', name: 'roofSizeSet', value: INPUT_MODE.PDF, message: 'modal.placement.initial.setting.size.pdf' },
|
|
]
|
|
|
|
const handleInputModeChange = (value) => {
|
|
setInputMode(value)
|
|
if (value === INPUT_MODE.PDF) {
|
|
// PDF 모드는 내부적으로 복시도(1) 로 저장된다.
|
|
setCurrentRoof((prev) => ({ ...prev, roofSizeSet: INPUT_MODE.SIZE_ROOF }))
|
|
} else {
|
|
setCurrentRoof((prev) => ({ ...prev, roofSizeSet: value }))
|
|
}
|
|
}
|
|
|
|
const resetPdfFile = () => {
|
|
setPdfFile(null)
|
|
if (pdfInputRef.current) pdfInputRef.current.value = ''
|
|
}
|
|
|
|
const handlePdfFileChange = (e) => {
|
|
const next = e.target.files?.[0]
|
|
if (!next) return
|
|
if (next.type !== 'application/pdf') {
|
|
swalFire({ text: getMessage('modal.placement.initial.setting.size.pdf.error.mime'), type: 'alert' })
|
|
e.target.value = ''
|
|
return
|
|
}
|
|
if (next.size > PDF_MAX_FILE_BYTES) {
|
|
swalFire({ text: getMessage('modal.placement.initial.setting.size.pdf.error.too.large'), type: 'alert' })
|
|
e.target.value = ''
|
|
return
|
|
}
|
|
setPdfFile(next)
|
|
}
|
|
|
|
const analyzePdfAndApply = async () => {
|
|
if (!pdfFile) {
|
|
swalFire({ text: getMessage('modal.placement.initial.setting.size.pdf.error.no.file'), type: 'alert' })
|
|
return false
|
|
}
|
|
|
|
const formData = new FormData()
|
|
formData.append('file', pdfFile)
|
|
formData.append('unitHint', 'mm')
|
|
formData.append('pageMode', pdfPageMode)
|
|
if (pdfPageMode === PDF_PAGE_MODE.SPECIFY) {
|
|
formData.append('facadePages', pdfFacadePages.trim())
|
|
formData.append('floorPages', pdfFloorPages.trim())
|
|
}
|
|
|
|
setPdfAnalyzing(true)
|
|
let response
|
|
try {
|
|
response = await fetch('/api/gemini/floor-plan', { method: 'POST', body: formData })
|
|
} catch (e) {
|
|
logger.error('[PlacementShapeSetting] PDF analyze fetch failed', e?.message || e)
|
|
setPdfAnalyzing(false)
|
|
swalFire({ text: getMessage('pdf.import.error.network'), type: 'alert' })
|
|
return false
|
|
}
|
|
|
|
let payload
|
|
try {
|
|
payload = await response.json()
|
|
} catch (e) {
|
|
logger.error('[PlacementShapeSetting] PDF analyze response parse failed', e?.message || e)
|
|
setPdfAnalyzing(false)
|
|
swalFire({ text: getMessage('pdf.import.error.analyze'), type: 'alert' })
|
|
return false
|
|
}
|
|
|
|
setPdfAnalyzing(false)
|
|
|
|
if (!response.ok) {
|
|
const msg = payload?.error?.message || getMessage('pdf.import.error.analyze')
|
|
swalFire({ text: msg, type: 'alert' })
|
|
return false
|
|
}
|
|
if (payload.empty || !Array.isArray(payload.outerline) || payload.outerline.length === 0) {
|
|
swalFire({ text: payload.notes || getMessage('pdf.import.error.no.floorplan'), type: 'alert' })
|
|
return false
|
|
}
|
|
|
|
const applied = await applyOuterline(payload.outerline, { unit: payload.unit || 'mm' })
|
|
if (!applied) {
|
|
swalFire({ text: getMessage('pdf.import.error.apply'), type: 'alert' })
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 지붕각도 설정(경사/각도)
|
|
*/
|
|
const roofAngleSetArray = [
|
|
{ id: 'ra04', name: 'roofAngleSet', value: 'slope', message: 'modal.placement.initial.setting.roof.pitch' },
|
|
{ id: 'ra05', name: 'roofAngleSet', value: 'flat', message: 'modal.placement.initial.setting.roof.angle' },
|
|
]
|
|
|
|
/**
|
|
* 지붕재 초기값
|
|
*/
|
|
const DEFAULT_ROOF_SETTINGS = {
|
|
roofSizeSet: '1',
|
|
roofAngleSet: 'slope',
|
|
angle: 21.8,
|
|
hajebichi: null,
|
|
id: 'ROOF_ID_WA_53A',
|
|
index: 0,
|
|
layout: ROOF_MATERIAL_LAYOUT.PARALLEL,
|
|
lenAuth: 'R',
|
|
lenBase: '235.000',
|
|
length: 235,
|
|
name: '일본기와 A',
|
|
nameJp: '和瓦A',
|
|
pitch: 4,
|
|
planNo: planNo,
|
|
raft: '',
|
|
raftAuth: 'C',
|
|
raftBaseCd: 'HEI_455',
|
|
roofCd: '',
|
|
roofMatlCd: 'ROOF_ID_WA_53A',
|
|
roofMatlNm: '일본기와 A',
|
|
roofMatlNmJp: '和瓦A',
|
|
roofPchAuth: null,
|
|
roofPchBase: null,
|
|
selected: true,
|
|
widAuth: 'R',
|
|
widBase: '265.000',
|
|
width: 265,
|
|
}
|
|
|
|
useEffect(() => {
|
|
/**
|
|
* 메뉴에서 배치면초기설정 선택 시 조회 후 화면 오픈
|
|
*/
|
|
if (openPoint && openPoint === 'canvasMenus') fetchBasicSettings(planNo, openPoint)
|
|
}, [])
|
|
|
|
/**
|
|
* 현재 활성 플랜이 변경될 때 currentRoof.planNo 업데이트
|
|
*/
|
|
useEffect(() => {
|
|
if (currentCanvasPlan?.planNo && currentRoof) {
|
|
setCurrentRoof((prev) => ({ ...prev, planNo: currentCanvasPlan.planNo }))
|
|
}
|
|
}, [currentCanvasPlan?.planNo])
|
|
|
|
/**
|
|
* 배치면초기설정 데이터 조회 후 화면 오픈
|
|
*/
|
|
useEffect(() => {
|
|
if (addedRoofs.length > 0) {
|
|
const raftCodeList = findCommonCode('203800')
|
|
setRaftCodes(raftCodeList)
|
|
setCurrentRoof({
|
|
...addedRoofs[0],
|
|
planNo: currentCanvasPlan?.planNo || planNo,
|
|
roofSizeSet: String(basicSetting.roofSizeSet),
|
|
roofAngleSet: basicSetting.roofAngleSet,
|
|
})
|
|
} else {
|
|
/** 데이터 설정 확인 후 데이터가 없으면 기본 데이터 설정 */
|
|
setCurrentRoof({ ...DEFAULT_ROOF_SETTINGS })
|
|
}
|
|
}, [addedRoofs])
|
|
|
|
/**
|
|
* 배치면초기설정 정보 변경 시 basicSettings 설정
|
|
*/
|
|
useEffect(() => {
|
|
if (!currentRoof) return
|
|
setBasicSettings({
|
|
...basicSetting,
|
|
planNo: Number(currentRoof.planNo),
|
|
roofSizeSet: String(currentRoof.roofSizeSet),
|
|
roofAngleSet: currentRoof.roofAngleSet,
|
|
roofsData: {
|
|
planNo: Number(currentRoof.planNo),
|
|
roofApply: true,
|
|
roofSeq: 0,
|
|
roofMatlCd: currentRoof.roofMatlCd,
|
|
roofWidth: currentRoof.width,
|
|
roofHeight: currentRoof.length,
|
|
roofHajebichi: currentRoof.hajebichi,
|
|
roofGap: currentRoof.raft,
|
|
roofLayout: currentRoof.layout,
|
|
roofPitch: currentRoof.pitch,
|
|
roofAngle: currentRoof.angle,
|
|
},
|
|
})
|
|
}, [currentRoof])
|
|
|
|
const handleRoofAngleSetChange = (value) => {
|
|
setCurrentRoof({ ...currentRoof, roofAngleSet: value })
|
|
}
|
|
|
|
/**
|
|
* 지붕재 변경 시 현재 지붕 정보 변경
|
|
*/
|
|
const handleRoofTypeChange = (value) => {
|
|
const selectedRoofMaterial = roofMaterials.find((roof) => roof.roofMatlCd === value)
|
|
|
|
setCurrentRoof({
|
|
...selectedRoofMaterial,
|
|
// pitch: currentRoof?.pitch,
|
|
// angle: currentRoof?.angle,
|
|
index: 0,
|
|
planNo: currentRoof.planNo,
|
|
roofSizeSet: String(currentRoof.roofSizeSet),
|
|
roofAngleSet: currentRoof.roofAngleSet,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 입력 값 변경 시 현재 지붕 정보 변경
|
|
*/
|
|
const changeInput = (value, e) => {
|
|
const { name } = e.target
|
|
setCurrentRoof({ ...currentRoof, [name]: Number(normalizeDecimal(value)) })
|
|
}
|
|
|
|
/**
|
|
* 서까래 변경 시 현재 지붕 정보 변경
|
|
*/
|
|
const handleRafterChange = (value) => {
|
|
setCurrentRoof({ ...currentRoof, raft: value })
|
|
}
|
|
|
|
/**
|
|
* 지붕 레이아웃 변경 시 현재 지붕 정보 변경
|
|
*/
|
|
const handleRoofLayoutChange = (value) => {
|
|
if (+currentRoof.roofSizeSet === 3) {
|
|
setCurrentRoof({ ...currentRoof, layout: ROOF_MATERIAL_LAYOUT.PARALLEL })
|
|
return
|
|
}
|
|
setCurrentRoof({ ...currentRoof, layout: value })
|
|
}
|
|
|
|
/**
|
|
* 배치면초기설정 저장 버튼 클릭
|
|
*/
|
|
const handleSaveBtn = async () => {
|
|
if (pdfAnalyzing) return
|
|
|
|
if (inputMode === INPUT_MODE.PDF) {
|
|
const ok = await analyzePdfAndApply()
|
|
if (!ok) return
|
|
}
|
|
|
|
const roofInfo = {
|
|
...currentRoof,
|
|
planNo: currentCanvasPlan?.planNo || basicSetting.planNo,
|
|
roofCd: roofRef.roofCd.current?.value,
|
|
width: roofRef.width.current?.value,
|
|
length: roofRef.length.current?.value,
|
|
hajebichi: roofRef.hajebichi.current?.value,
|
|
//raft: roofRef.rafter.current?.value,
|
|
selected: true,
|
|
layout: currentRoof.layout,
|
|
index: 0,
|
|
}
|
|
|
|
// [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 제거
|
|
debugCapture.log('LOW-PITCH-DIAG handleSaveBtn', {
|
|
roofMatlCd: roofInfo.roofMatlCd,
|
|
pitch: roofInfo.pitch,
|
|
angle: roofInfo.angle,
|
|
idMatched: LOW_PITCH_RESTRICTED_ROOF_IDS.has(roofInfo.roofMatlCd),
|
|
restricted: isLowPitchRestricted(roofInfo),
|
|
})
|
|
|
|
// [LOW-PITCH-WARN 2026-05-06] 6종 기와 + (2 ≤ 寸 < 2.5 OR 11.31 ≤ 度 < 14.04) 일 때 안내 alert. OK 누르면 저장 진행
|
|
await notifyLowPitchRestrictionForRoofs([roofInfo], swalFire, getMessage)
|
|
|
|
const newAddedRoofs = [...addedRoofs]
|
|
newAddedRoofs[0] = { ...roofInfo }
|
|
setAddedRoofs(newAddedRoofs)
|
|
|
|
// currentRoof의 roofSizeSet을 canvasSetting에 반영
|
|
setCanvasSetting({ ...canvasSetting, roofSizeSet: currentRoof?.roofSizeSet })
|
|
|
|
/**
|
|
* 배치면초기설정 저장 (메뉴 변경/useEffect 트리거 없이)
|
|
*/
|
|
basicSettingSave(
|
|
{
|
|
...basicSetting,
|
|
planNo: currentCanvasPlan?.planNo || basicSetting.planNo,
|
|
/**
|
|
* 선택된 지붕재 정보
|
|
*/
|
|
selectedRoofMaterial: {
|
|
...newAddedRoofs[0],
|
|
},
|
|
},
|
|
{ skipSideEffects: true },
|
|
)
|
|
|
|
const roofs = canvas.getObjects().filter((obj) => obj.roofMaterial?.index === 0)
|
|
|
|
roofs.forEach((roof) => {
|
|
/** 모양 패턴 설정 */
|
|
setSurfaceShapePattern(roof, roofDisplay.column, false, { ...roofInfo })
|
|
roof.roofMaterial = { ...roofInfo }
|
|
drawDirectionArrow(roof)
|
|
setPolygonLinesActualSize(roof, true)
|
|
})
|
|
canvas.renderAll()
|
|
|
|
/** 지붕면 존재 여부에 따라 메뉴 설정 */
|
|
const hasRoofs = canvas.getObjects().some((obj) => obj.name === POLYGON_TYPE.ROOF)
|
|
|
|
// roofSizeSet에 따라 메뉴 설정
|
|
if (currentRoof?.roofSizeSet === '2') {
|
|
setSelectedMenu('surface')
|
|
setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING)
|
|
} else if (currentRoof?.roofSizeSet === '1') {
|
|
setSelectedMenu('outline')
|
|
setCurrentMenu(MENU.ROOF_COVERING.EXTERIOR_WALL_LINE)
|
|
} else if (hasRoofs) {
|
|
setSelectedMenu('surface')
|
|
setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING)
|
|
} else {
|
|
setSelectedMenu('outline')
|
|
setCurrentMenu(MENU.ROOF_COVERING.EXTERIOR_WALL_LINE)
|
|
}
|
|
|
|
/* 저장 후 화면 닫기 */
|
|
closePopup(id)
|
|
await saveCanvas(false)
|
|
}
|
|
|
|
return (
|
|
<WithDraggable isShow={true} pos={pos} className="ll">
|
|
<WithDraggable.Header title={getMessage('plan.menu.placement.surface.initial.setting')} />
|
|
<WithDraggable.Body>
|
|
<div className="placement-table">
|
|
<table>
|
|
<colgroup>
|
|
<col style={{ width: '151px' }} />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>
|
|
<tr>
|
|
<th>{getMessage('modal.placement.initial.setting.plan.drawing')}</th>
|
|
<td>
|
|
{getMessage('modal.placement.initial.setting.plan.drawing.size.stuff')}
|
|
|
|
{getMessage('modal.placement.initial.setting.plan.drawing.only.number')}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>
|
|
<div className="tip-wrap">
|
|
{getMessage('modal.placement.initial.setting.size')}
|
|
<button className="tooltip" onClick={() => setShowSizeGuidModal(true)}></button>
|
|
</div>
|
|
</th>
|
|
<td>
|
|
<div className="pop-form-radio">
|
|
{currentRoof &&
|
|
roofSizeSetArray.map((item) => (
|
|
<div className="d-check-radio pop" key={item.id}>
|
|
<input
|
|
type="radio"
|
|
id={item.id}
|
|
name={item.name}
|
|
value={item.value}
|
|
checked={inputMode === item.value}
|
|
onChange={(e) => handleInputModeChange(e.target.value)}
|
|
/>
|
|
<label htmlFor={item.id}>{getMessage(item.message)}</label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
{inputMode === INPUT_MODE.PDF && (
|
|
<div
|
|
className="pdf-info-text"
|
|
style={{ fontSize: '12px', lineHeight: 1.5, color: '#bbb', whiteSpace: 'pre-line', marginTop: '8px' }}
|
|
>
|
|
{getMessage('modal.placement.initial.setting.size.pdf.info')}
|
|
</div>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
{inputMode === INPUT_MODE.PDF && (
|
|
<>
|
|
<tr>
|
|
<th>{getMessage('modal.placement.initial.setting.size.pdf.file.label')}</th>
|
|
<td>
|
|
<div className="img-flex-box" style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
<div className="img-edit-wrap">
|
|
<label className={`img-edit-btn ${pdfAnalyzing ? 'no-click' : ''}`} htmlFor="pdf_import_file">
|
|
<span className="img-edit"></span>
|
|
{getMessage('modal.placement.initial.setting.size.pdf.file.upload')}
|
|
</label>
|
|
<input
|
|
ref={pdfInputRef}
|
|
type="file"
|
|
id="pdf_import_file"
|
|
accept="application/pdf"
|
|
style={{ display: 'none' }}
|
|
disabled={pdfAnalyzing}
|
|
onChange={handlePdfFileChange}
|
|
/>
|
|
</div>
|
|
<div className="img-name-wrap" style={{ flex: 1, display: 'flex', alignItems: 'center' }}>
|
|
<input
|
|
type="text"
|
|
className="input-origin al-l"
|
|
value={pdfFile?.name ?? ''}
|
|
readOnly
|
|
placeholder={getMessage('modal.placement.initial.setting.size.pdf.file.placeholder')}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
{pdfFile && !pdfAnalyzing && <button className="img-check" onClick={resetPdfFile}></button>}
|
|
</div>
|
|
</div>
|
|
<div style={{ fontSize: '12px', color: '#bbb', marginTop: '4px' }}>
|
|
{getMessage('modal.placement.initial.setting.size.pdf.file.info')}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>{getMessage('modal.placement.initial.setting.size.pdf.page.label')}</th>
|
|
<td>
|
|
<div className="pop-form-radio" style={{ alignItems: 'center', gap: '12px' }}>
|
|
<div className="d-check-radio pop">
|
|
<input
|
|
type="radio"
|
|
id="pdf-page-all"
|
|
name="pdfPageMode"
|
|
value={PDF_PAGE_MODE.ALL}
|
|
checked={pdfPageMode === PDF_PAGE_MODE.ALL}
|
|
onChange={(e) => setPdfPageMode(e.target.value)}
|
|
/>
|
|
<label htmlFor="pdf-page-all">{getMessage('modal.placement.initial.setting.size.pdf.page.all')}</label>
|
|
</div>
|
|
<div className="d-check-radio pop">
|
|
<input
|
|
type="radio"
|
|
id="pdf-page-specify"
|
|
name="pdfPageMode"
|
|
value={PDF_PAGE_MODE.SPECIFY}
|
|
checked={pdfPageMode === PDF_PAGE_MODE.SPECIFY}
|
|
onChange={(e) => setPdfPageMode(e.target.value)}
|
|
/>
|
|
<label htmlFor="pdf-page-specify">{getMessage('modal.placement.initial.setting.size.pdf.page.specify')}</label>
|
|
</div>
|
|
{pdfPageMode === PDF_PAGE_MODE.SPECIFY && (
|
|
<>
|
|
<span style={{ fontSize: '13px' }}>{getMessage('modal.placement.initial.setting.size.pdf.page.facade.label')}</span>
|
|
<input
|
|
type="text"
|
|
className="input-origin"
|
|
style={{ width: '90px' }}
|
|
placeholder={getMessage('modal.placement.initial.setting.size.pdf.page.example.facade')}
|
|
value={pdfFacadePages}
|
|
onChange={(e) => setPdfFacadePages(e.target.value)}
|
|
/>
|
|
<span style={{ fontSize: '13px' }}>{getMessage('modal.placement.initial.setting.size.pdf.page.floor.label')}</span>
|
|
<input
|
|
type="text"
|
|
className="input-origin"
|
|
style={{ width: '90px' }}
|
|
placeholder={getMessage('modal.placement.initial.setting.size.pdf.page.example.floor')}
|
|
value={pdfFloorPages}
|
|
onChange={(e) => setPdfFloorPages(e.target.value)}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div style={{ fontSize: '12px', color: '#bbb', marginTop: '4px' }}>
|
|
{pdfPageMode === PDF_PAGE_MODE.SPECIFY
|
|
? getMessage('modal.placement.initial.setting.size.pdf.page.specify.info')
|
|
: getMessage('modal.placement.initial.setting.size.pdf.page.all.info')}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</>
|
|
)}
|
|
<tr>
|
|
<th>
|
|
<div className="tip-wrap">
|
|
{getMessage('modal.placement.initial.setting.roof.material')}
|
|
<button className="tooltip" onClick={() => setShowMaterialGuidModal(true)}></button>
|
|
</div>
|
|
</th>
|
|
<td>
|
|
<div className="placement-option">
|
|
<div className="grid-select no-flx" style={{ width: '171px' }}>
|
|
<QSelectBox
|
|
title={
|
|
currentRoof?.roofSizeSet === '3'
|
|
? getMessage('modal.placement.initial.setting.size.none.pitch')
|
|
: globalLocale === 'ko'
|
|
? currentRoof?.roofMatlNm
|
|
: currentRoof?.roofMatlNmJp
|
|
}
|
|
options={roofMaterials.map((roof) => {
|
|
return { ...roof, name: globalLocale === 'ko' ? roof.roofMatlNm : roof.roofMatlNmJp }
|
|
})}
|
|
value={currentRoof?.roofSizeSet === '3' ? null : currentRoof?.roofMatlCd}
|
|
onChange={(e) => handleRoofTypeChange(e.roofMatlCd)}
|
|
sourceKey="id"
|
|
targetKey="id"
|
|
showKey="name"
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
/>
|
|
</div>
|
|
{currentRoof && ['R', 'C'].includes(currentRoof.widAuth) && (
|
|
<div className="flex-ment">
|
|
<span>W</span>
|
|
<div className="input-grid" style={{ width: '84px' }}>
|
|
{/*<input*/}
|
|
{/* type="text"*/}
|
|
{/* className="input-origin block"*/}
|
|
{/* name={`width`}*/}
|
|
{/* ref={roofRef.width}*/}
|
|
{/* value={parseInt(currentRoof?.width)}*/}
|
|
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
|
|
{/* readOnly={currentRoof?.widAuth === 'R'}*/}
|
|
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
|
|
{/*/>*/}
|
|
|
|
<CalculatorInput
|
|
id=""
|
|
name={'width'}
|
|
label=""
|
|
className="input-origin block"
|
|
ref={roofRef.width}
|
|
value={currentRoof?.width || 0}
|
|
onChange={(value) => {
|
|
setCurrentRoof({ ...currentRoof, value })
|
|
}}
|
|
readOnly={currentRoof?.widAuth === 'R'}
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
disableKeypad={disableKeypad}
|
|
options={{
|
|
allowNegative: false,
|
|
allowDecimal: false, //(index !== 0),
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{currentRoof && ['R', 'C'].includes(currentRoof.lenAuth) && (
|
|
<div className="flex-ment">
|
|
<span>L</span>
|
|
<div className="input-grid" style={{ width: '84px' }}>
|
|
{/*<input*/}
|
|
{/* type="text"*/}
|
|
{/* className="input-origin block"*/}
|
|
{/* name={`length`}*/}
|
|
{/* ref={roofRef.length}*/}
|
|
{/* value={parseInt(currentRoof?.length)}*/}
|
|
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
|
|
{/* readOnly={currentRoof?.lenAuth === 'R'}*/}
|
|
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
|
|
{/*/>*/}
|
|
|
|
<CalculatorInput
|
|
id=""
|
|
name={'length'}
|
|
label=""
|
|
className="input-origin block"
|
|
ref={roofRef.length}
|
|
value={currentRoof?.length || 0}
|
|
onChange={(value) => {
|
|
setCurrentRoof({ ...currentRoof, value })
|
|
}}
|
|
readOnly={currentRoof?.lenAuth === 'R'}
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
disableKeypad={disableKeypad}
|
|
options={{
|
|
allowNegative: false,
|
|
allowDecimal: false, //(index !== 0),
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{currentRoof && ['C', 'R'].includes(currentRoof.raftAuth) && (
|
|
<div className="flex-ment">
|
|
<span>{getMessage('modal.placement.initial.setting.rafter')}</span>
|
|
{raftCodes?.length > 0 && (
|
|
<div className="select-wrap" style={{ width: '160px' }}>
|
|
<QSelectBox
|
|
options={raftCodes}
|
|
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'}
|
|
showKey="clCodeNm"
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
{currentRoof && ['C', 'R'].includes(currentRoof?.roofPchAuth) && (
|
|
<div className="flex-ment">
|
|
<span>{getMessage('hajebichi')}</span>
|
|
<div className="input-grid" style={{ width: '84px' }}>
|
|
{/*<input*/}
|
|
{/* type="text"*/}
|
|
{/* className="input-origin block"*/}
|
|
{/* name={`hajebichi`}*/}
|
|
{/* ref={roofRef.hajebichi}*/}
|
|
{/* value={parseInt(currentRoof?.hajebichi)}*/}
|
|
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
|
|
{/* readOnly={currentRoof?.roofPchAuth === 'R'}*/}
|
|
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
|
|
{/*/>*/}
|
|
<CalculatorInput
|
|
id=""
|
|
name="hajebichi"
|
|
label=""
|
|
className="input-origin block"
|
|
ref={roofRef.hajebichi}
|
|
value={currentRoof?.hajebichi ?? basicSetting?.roofPchBase ?? '0'}
|
|
onChange={(value) => {
|
|
const hajebichi = value === '' ? '' : Number(value)
|
|
setCurrentRoof((prev) => ({
|
|
...prev,
|
|
hajebichi,
|
|
}))
|
|
}}
|
|
readOnly={currentRoof?.roofPchAuth === 'R'}
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
disableKeypad={disableKeypad}
|
|
options={{
|
|
allowNegative: false,
|
|
allowDecimal: false, //(index !== 0),
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{/* {currentRoof && (
|
|
<div className="placement-roof-btn-wrap">
|
|
<div className="icon-btn-wrap mt10">
|
|
<button
|
|
className={`${+currentRoof.roofSizeSet !== 3 && currentRoof?.layout === ROOF_MATERIAL_LAYOUT.PARALLEL && 'act'}`}
|
|
value={ROOF_MATERIAL_LAYOUT.PARALLEL}
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
onClick={() => handleRoofLayoutChange(ROOF_MATERIAL_LAYOUT.PARALLEL)}
|
|
>
|
|
{getMessage('modal.roof.alloc.select.parallel')}
|
|
<i className="allocation01"></i>
|
|
</button>
|
|
<button
|
|
className={`${+currentRoof.roofSizeSet !== 3 && currentRoof.layout === ROOF_MATERIAL_LAYOUT.STAIRS && 'act'}`}
|
|
disabled={currentRoof?.roofSizeSet === '3'}
|
|
value={ROOF_MATERIAL_LAYOUT.STAIRS}
|
|
onClick={() => handleRoofLayoutChange(ROOF_MATERIAL_LAYOUT.STAIRS)}
|
|
>
|
|
{getMessage('modal.roof.alloc.select.stairs')} <i className="allocation02"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)} */}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>{getMessage('modal.placement.initial.setting.roof.angle.setting')}</th>
|
|
<td>
|
|
<div className="pop-form-radio place">
|
|
{currentRoof &&
|
|
roofAngleSetArray.map((item, index) => (
|
|
<div className="outline-form" key={item.id}>
|
|
<span>
|
|
<div className="d-check-radio pop">
|
|
<input
|
|
type="radio"
|
|
id={item.id}
|
|
name={item.name}
|
|
value={item.value}
|
|
checked={String(currentRoof?.roofAngleSet) === item.value}
|
|
onChange={(e) => setCurrentRoof({ ...currentRoof, roofAngleSet: e.target.value })}
|
|
/>
|
|
<label htmlFor={item.id}>{getMessage(item.message)}</label>
|
|
</div>
|
|
</span>
|
|
<div className="input-grid mr5">
|
|
{/* <input
|
|
type="text"
|
|
className="input-origin block"
|
|
readOnly={currentRoof?.roofAngleSet !== item.value}
|
|
value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'}
|
|
onChange={(e) => {
|
|
const v = normalizeDecimal(e.target.value)
|
|
e.target.value = v
|
|
if (index === 0) {
|
|
const num = v === '' ? '' : Number(v)
|
|
setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : num, angle: num === '' ? '' : getDegreeByChon(num) })
|
|
} else {
|
|
const num = v === '' ? '' : Number(v)
|
|
setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : getChonByDegree(num), angle: num === '' ? '' : num })
|
|
}
|
|
}}
|
|
/> */}
|
|
<CalculatorInput
|
|
id=""
|
|
name=""
|
|
label=""
|
|
className="input-origin block"
|
|
readOnly={currentRoof?.roofAngleSet !== item.value}
|
|
value={index === 0 ? (currentRoof?.pitch ?? basicSetting?.inclBase ?? '0') : (currentRoof?.angle ?? '0')}
|
|
onChange={(value) => {
|
|
if (index === 0) {
|
|
const pitch = value === '' ? '' : Number(value)
|
|
const angle = pitch === '' ? '' : getDegreeByChon(pitch)
|
|
setCurrentRoof((prev) => ({
|
|
...prev,
|
|
pitch,
|
|
angle,
|
|
}))
|
|
} else {
|
|
const angle = value === '' ? '' : Number(value)
|
|
const pitch = angle === '' ? '' : getChonByDegree(angle)
|
|
setCurrentRoof((prev) => ({
|
|
...prev,
|
|
pitch,
|
|
angle,
|
|
}))
|
|
}
|
|
}}
|
|
disableKeypad={disableKeypad}
|
|
options={{
|
|
allowNegative: false,
|
|
allowDecimal: true,
|
|
}}
|
|
/>
|
|
</div>
|
|
<span className="thin">{index === 0 ? '寸' : '度'}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div className="grid-btn-wrap" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '5px' }}>
|
|
<button style={{ width: 'auto' }} 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 ${pdfAnalyzing || (inputMode === INPUT_MODE.PDF && !pdfFile) ? 'no-click' : ''}`}
|
|
onClick={handleSaveBtn}
|
|
disabled={pdfAnalyzing || (inputMode === INPUT_MODE.PDF && !pdfFile)}
|
|
>
|
|
{pdfAnalyzing ? getMessage('pdf.import.button.analyzing') : getMessage('modal.common.save')}
|
|
</button>
|
|
</div>
|
|
</WithDraggable.Body>
|
|
{showSizeGuideModal && <SizeGuide setShowSizeGuidModal={setShowSizeGuidModal} />}
|
|
{showMaterialGuideModal && <MaterialGuide setShowMaterialGuidModal={setShowMaterialGuidModal} />}
|
|
</WithDraggable>
|
|
)
|
|
}
|