diff --git a/src/components/auth/Join.jsx b/src/components/auth/Join.jsx index 94f7fa3e..a4c17e33 100644 --- a/src/components/auth/Join.jsx +++ b/src/components/auth/Join.jsx @@ -35,7 +35,12 @@ export default function Join() { const joinValidation = (formData) => { // 전화번호/FAX 정규식 (일본 형식: 0으로 시작, 하이픈 포함) - const telRegex = /^0\d{1,4}-\d{1,4}-\d{4}$/ + const telRegex = /^0\d{1,4}-\d{1,4}-\d{3,4}$/ + const isValidTel = (value) => { + if (!telRegex.test(value)) return false + const digitCount = value.replace(/-/g, '').length + return digitCount >= 10 && digitCount <= 11 + } // 판매대리점 정보 - 판매대리점명 @@ -77,7 +82,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) telNoRef.current.focus() return false - } else if (!telRegex.test(telNo)) { + } else if (!isValidTel(telNo)) { alert(getMessage('join.validation.check1', [getMessage('join.sub1.telNo')])) telNoRef.current.focus() return false @@ -98,7 +103,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub1.fax')])) faxRef.current.focus() return false - }else if (!telRegex.test(fax)) { + }else if (!isValidTel(fax)) { alert(getMessage('join.validation.check1', [getMessage('join.sub1.fax')])) faxRef.current.focus() return false @@ -173,7 +178,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) userTelNoRef.current.focus() return false - } else if (!telRegex.test(userTelNo)) { + } else if (!isValidTel(userTelNo)) { alert(getMessage('join.validation.check1', [getMessage('join.sub1.telNo')])) userTelNoRef.current.focus() return false @@ -185,7 +190,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub2.fax')])) userFaxRef.current.focus() return false - } else if (!telRegex.test(userFax)) { + } else if (!isValidTel(userFax)) { alert(getMessage('join.validation.check1', [getMessage('join.sub2.fax')])) userFaxRef.current.focus() return false diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 95442b8a..a537b065 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -8,7 +8,7 @@ import MaterialGuide from '@/components/floor-plan/modal/placementShape/Material import WithDraggable from '@/components/common/draggable/WithDraggable' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' -import { useRecoilState, useRecoilValue } from 'recoil' +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' @@ -16,11 +16,12 @@ import { globalLocaleStore } from '@/store/localeAtom' import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { usePolygon } from '@/hooks/usePolygon' -import { canvasState } from '@/store/canvasAtom' +import { canvasState, 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, normalizeDigits } from '@/util/input-utils' -import { logger } from '@/util/logger' +import { normalizeDecimal } from '@/util/input-utils' import { CalculatorInput } from '@/components/common/input/CalcInput' /** @@ -41,14 +42,21 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla 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 [raftCodes, setRaftCodes] = useState([]) + /** 서까래 정보 */ + const [currentRoof, setCurrentRoof] = useState(null) + /** 현재 선택된 지붕재 정보 */ + const { closePopup } = usePopup() + /** usePopup에서 closePopup 함수 가져오기 */ const { drawDirectionArrow } = usePolygon() const { setSurfaceShapePattern } = useRoofFn() const canvas = useRecoilValue(canvasState) const roofDisplay = useRecoilValue(roofDisplaySelector) + const { setPolygonLinesActualSize } = usePolygon() + const { setSelectedMenu } = useCanvasMenu() + const setCurrentMenu = useSetRecoilState(currentMenuState) + const roofRef = { roofCd: useRef(null), width: useRef(null), @@ -121,7 +129,12 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla if (addedRoofs.length > 0) { const raftCodeList = findCommonCode('203800') setRaftCodes(raftCodeList) - setCurrentRoof({ ...addedRoofs[0], planNo: planNo, roofSizeSet: String(basicSetting.roofSizeSet), roofAngleSet: basicSetting.roofAngleSet }) + setCurrentRoof({ + ...addedRoofs[0], + planNo: planNo, + roofSizeSet: String(basicSetting.roofSizeSet), + roofAngleSet: basicSetting.roofAngleSet, + }) } else { /** 데이터 설정 확인 후 데이터가 없으면 기본 데이터 설정 */ setCurrentRoof({ ...DEFAULT_ROOF_SETTINGS }) @@ -209,7 +222,6 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla * 배치면초기설정 저장 버튼 클릭 */ const handleSaveBtn = async () => { - const roofInfo = { ...currentRoof, planNo: basicSetting.planNo, @@ -227,7 +239,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla newAddedRoofs[0] = { ...roofInfo } setAddedRoofs(newAddedRoofs) - logger.debug('save Info', { + console.log('save Info', { ...basicSetting, selectedRoofMaterial: { ...newAddedRoofs[0], @@ -235,7 +247,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla }) /** - * 배치면초기설정 저장 + * 배치면초기설정 저장 (메뉴 변경/useEffect 트리거 없이) */ basicSettingSave({ ...basicSetting, @@ -245,7 +257,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla selectedRoofMaterial: { ...newAddedRoofs[0], }, - }) + }, { skipSideEffects: true }) const roofs = canvas.getObjects().filter((obj) => obj.roofMaterial?.index === 0) @@ -254,7 +266,19 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla 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) + if (hasRoofs) { + setSelectedMenu('surface') + setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING) + } else { + setSelectedMenu('outline') + setCurrentMenu(MENU.ROOF_COVERING.EXTERIOR_WALL_LINE) + } /* 저장 후 화면 닫기 */ closePopup(id) @@ -353,26 +377,26 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla 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 => ({ + const pitch = value === '' ? '' : Number(value) + const angle = pitch === '' ? '' : getDegreeByChon(pitch) + setCurrentRoof((prev) => ({ ...prev, pitch, - angle - })); + angle, + })) } else { - const angle = value === '' ? '' : Number(value); - const pitch = angle === '' ? '' : getChonByDegree(angle); - setCurrentRoof(prev => ({ + const angle = value === '' ? '' : Number(value) + const pitch = angle === '' ? '' : getChonByDegree(angle) + setCurrentRoof((prev) => ({ ...prev, pitch, - angle - })); + angle, + })) } }} options={{ allowNegative: false, - allowDecimal: true + allowDecimal: true, }} /> @@ -432,7 +456,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla label="" className="input-origin block" ref={roofRef.width} - value={currentRoof?.width||0} + value={currentRoof?.width || 0} onChange={(value) => { setCurrentRoof({ ...currentRoof, value }) }} @@ -440,7 +464,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla disabled={currentRoof?.roofSizeSet === '3'} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: false, //(index !== 0), }} /> @@ -467,7 +491,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla label="" className="input-origin block" ref={roofRef.length} - value={currentRoof?.length||0} + value={currentRoof?.length || 0} onChange={(value) => { setCurrentRoof({ ...currentRoof, value }) }} @@ -475,7 +499,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla disabled={currentRoof?.roofSizeSet === '3'} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: false, //(index !== 0), }} /> @@ -522,20 +546,19 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla ref={roofRef.hajebichi} value={currentRoof?.hajebichi ?? basicSetting?.roofPchBase ?? '0'} onChange={(value) => { - const hajebichi = value === '' ? '' : Number(value); - setCurrentRoof(prev => ({ + const hajebichi = value === '' ? '' : Number(value) + setCurrentRoof((prev) => ({ ...prev, - hajebichi - })); + hajebichi, + })) }} readOnly={currentRoof?.roofPchAuth === 'R'} disabled={currentRoof?.roofSizeSet === '3'} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: false, //(index !== 0), }} /> - )} diff --git a/src/hooks/common/useCommonUtils.js b/src/hooks/common/useCommonUtils.js index 0b028f32..fc4c907b 100644 --- a/src/hooks/common/useCommonUtils.js +++ b/src/hooks/common/useCommonUtils.js @@ -633,9 +633,7 @@ export function useCommonUtils() { // 원본 roof의 자식 오브젝트들 찾기 (개구, 그림자, 도머 등) const childObjectTypes = [BATCH_TYPE.OPENING, BATCH_TYPE.SHADOW, BATCH_TYPE.TRIANGLE_DORMER, BATCH_TYPE.PENTAGON_DORMER] - const childObjects = canvas.getObjects().filter( - (o) => o.parentId === obj.id && childObjectTypes.includes(o.name) - ) + const childObjects = canvas.getObjects().filter((o) => o.parentId === obj.id && childObjectTypes.includes(o.name)) // 원본 roof 중심점 계산 const originalPoints = obj.getCurrentPoints() @@ -669,24 +667,28 @@ export function useCommonUtils() { y: p.y + offsetY, })) - clonedObj = new QPolygon(newPoints, { - fill: obj.fill || 'transparent', - stroke: obj.stroke || 'black', - strokeWidth: obj.strokeWidth || 1, - fontSize: 0, // 이동 중에는 lengthText 생성하지 않음 (fontSize=0이면 addLengthText가 스킵됨) - selectable: true, - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - name: 'clonedObj', - originX: 'center', - originY: 'center', - pitch: obj.pitch, - surfaceId: obj.surfaceId, - sort: false, - }, canvas) + clonedObj = new QPolygon( + newPoints, + { + fill: obj.fill || 'transparent', + stroke: obj.stroke || 'black', + strokeWidth: obj.strokeWidth || 1, + fontSize: 0, // 이동 중에는 lengthText 생성하지 않음 (fontSize=0이면 addLengthText가 스킵됨) + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'clonedObj', + originX: 'center', + originY: 'center', + pitch: obj.pitch, + surfaceId: obj.surfaceId, + // sort: false, + }, + canvas, + ) canvas.add(clonedObj) diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 88c32d5f..be906baa 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -440,15 +440,17 @@ export function useCanvasSetting(executeEffect = true) { }) setAddedRoofs(addRoofs) - setCanvasSetting({ - ...basicSetting, - roofMaterials: addRoofs[0], - planNo: roofsRow[0].planNo, - roofSizeSet: roofsRow[0].roofSizeSet, - roofAngleSet: roofsRow[0].roofAngleSet, - roofsData: roofsArray, - selectedRoofMaterial: addRoofs.find((roof) => roof.selected), - }) + if (openPoint !== 'basicSettingSave') { + setCanvasSetting({ + ...basicSetting, + roofMaterials: addRoofs[0], + planNo: roofsRow[0].planNo, + roofSizeSet: roofsRow[0].roofSizeSet, + roofAngleSet: roofsRow[0].roofAngleSet, + roofsData: roofsArray, + selectedRoofMaterial: addRoofs.find((roof) => roof.selected), + }) + } } }) } catch (error) { @@ -474,7 +476,8 @@ export function useCanvasSetting(executeEffect = true) { /** * 기본설정(PlacementShapeSetting) 저장 */ - const basicSettingSave = async (params) => { + const basicSettingSave = async (params, options = {}) => { + const { skipSideEffects = false } = options try { const patternData = { objectNo: correntObjectNo, @@ -527,14 +530,16 @@ export function useCanvasSetting(executeEffect = true) { setBasicSettings({ ...params }) }) - /** CanvasSetting Recoil 설정 - roofSizeSet을 문자열로 변환 */ - setCanvasSetting({ - ...basicSetting, - roofSizeSet: String(params.roofSizeSet), - }) + if (!skipSideEffects) { + /** CanvasSetting Recoil 설정 - roofSizeSet을 문자열로 변환 */ + setCanvasSetting({ + ...basicSetting, + roofSizeSet: String(params.roofSizeSet), + }) - /** 메뉴 설정 */ - setMenuByRoofSize(params.roofSizeSet) + /** 메뉴 설정 */ + setMenuByRoofSize(params.roofSizeSet) + } /** 배치면초기설정 조회 */ fetchBasicSettings(params.planNo, 'basicSettingSave') diff --git a/src/locales/ja.json b/src/locales/ja.json index 0c589e1e..2953688e 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -683,7 +683,7 @@ "join.sub1.addr": "住所", "join.sub1.addr_placeholder": "全角50文字以内", "join.sub1.telNo": "電話番号", - "join.sub1.telNo_placeholder": "00-0000-0000", + "join.sub1.telNo_placeholder": "000-0000-0000 / 0000-000-000", "join.sub1.fax": "FAX番号", "join.sub1.fax_placeholder": "00-0000-0000", "join.sub1.bizNo": "法人番号", @@ -702,7 +702,7 @@ "join.complete.title": "HANASYS設計ログインID発行申請完了", "join.complete.contents": "※申請したIDが承認されると、担当者情報に入力したEメールアドレスにログイン関連案内メールが送信されます。", "join.complete.email_comment": "担当者のメールアドレス", - "join.validation.check1": "{0}形式を確認してください。", + "join.validation.check1": "{0}形式(000-0000-0000 / 0000-000-000)を確認してください。", "join.complete.save.confirm": "ID申請を完了後は申請情報の修正が出来ません。申請しますか?", "stuff.gridHeader.lastEditDatetime": "更新日時", "stuff.gridHeader.objectNo": "物件番号", diff --git a/src/locales/ko.json b/src/locales/ko.json index 6ada9943..7e871e60 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -683,7 +683,7 @@ "join.sub1.addr": "주소", "join.sub1.addr_placeholder": "전각50자이내", "join.sub1.telNo": "전화번호", - "join.sub1.telNo_placeholder": "000-0000-0000", + "join.sub1.telNo_placeholder": "000-0000-0000 또는 0000-000-000", "join.sub1.fax": "FAX 번호", "join.sub1.fax_placeholder": "00 0000 0000", "join.sub1.bizNo": "법인번호", @@ -702,7 +702,7 @@ "join.complete.title": "Q.CAST3 로그인ID 발행신청 완료", "join.complete.contents": "※ 신청한 ID가 승인되면 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.", "join.complete.email_comment": "담당자 이메일 주소", - "join.validation.check1": "{0} 형식을 확인해주세요.", + "join.validation.check1": "{0} 형식(000-0000-0000 또는 0000-000-000)을 확인해주세요.", "join.complete.save.confirm": "Hanwha Japan 담당자에게 ID승인이 요청되면 더 이상 정보를 수정할 수 없습니다. 정말로 요청하시겠습니까?", "stuff.gridHeader.lastEditDatetime": "갱신일시", "stuff.gridHeader.objectNo": "물건번호",