diff --git a/package.json b/package.json index 303e4864..6c94a282 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-draggable": "^4.4.6", "react-hook-form": "^7.53.0", "react-icons": "^5.3.0", + "react-loading-skeleton": "^3.5.0", "react-responsive-modal": "^6.4.2", "recoil": "^0.7.7", "sweetalert2": "^11.14.1", diff --git a/src/components/auth/Join.jsx b/src/components/auth/Join.jsx index 3412efdb..a9e79750 100644 --- a/src/components/auth/Join.jsx +++ b/src/components/auth/Join.jsx @@ -1,53 +1,173 @@ 'use client' +import { useRef } from 'react' import { useAxios } from '@/hooks/useAxios' import { useRouter } from 'next/navigation' import { useMessage } from '@/hooks/useMessage' import Cookies from 'js-cookie' +import { isObjectNotEmpty, inputTelNumberCheck, inputNumberCheck } from '@/util/common-utils' + export default function Join() { const { getMessage } = useMessage() const { promisePost } = useAxios() const router = useRouter() + const storeQcastNmRef = useRef() + const storeQcastNmKanaRef = useRef() + const postCdRef = useRef() + const addrRef = useRef() + const telNoRef = useRef() + const faxRef = useRef() + const userNmRef = useRef() + const userIdRef = useRef() + const emailRef = useRef() + const userTelNoRef = useRef() + const userFaxRef = useRef() + + // 가입 신청 유효성 검사 + const joinValidation = (formData) => { + // 판매대리점 정보 - 판매대리점명 + const storeQcastNm = formData.get('storeQcastNm') + if (!isObjectNotEmpty(storeQcastNm)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.storeQcastNm')])) + storeQcastNmRef.current.focus() + return false + } + + // 판매대리점 정보 - 판매대리점명 후리가나 + const storeQcastNmKana = formData.get('storeQcastNmKana') + if (!isObjectNotEmpty(storeQcastNmKana)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.storeQcastNmKana')])) + storeQcastNmKanaRef.current.focus() + return false + } + + // 판매대리점 정보 - 우편번호 + const postCd = formData.get('postCd') + if (!isObjectNotEmpty(postCd)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.postCd')])) + postCdRef.current.focus() + return false + } + + // 판매대리점 정보 - 주소 + const addr = formData.get('addr') + if (!isObjectNotEmpty(addr)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.addr')])) + addrRef.current.focus() + return false + } + + // 판매대리점 정보 - 전화번호 + const telNo = formData.get('telNo') + if (!isObjectNotEmpty(telNo)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) + telNoRef.current.focus() + return false + } + + // 판매대리점 정보 - FAX 번호 + const fax = formData.get('fax') + if (!isObjectNotEmpty(fax)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.fax')])) + faxRef.current.focus() + return false + } + + // 담당자 정보 - 담당자명 + const userNm = formData.get('userNm') + if (!isObjectNotEmpty(userNm)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub2.userNm')])) + userNmRef.current.focus() + return false + } + + // 담당자 정보 - 신청 ID + const userId = formData.get('userId') + if (!isObjectNotEmpty(userId)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub2.userId')])) + userIdRef.current.focus() + return false + } + + // 담당자 정보 - 이메일 주소 + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + + const email = formData.get('email') + if (!isObjectNotEmpty(email)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub2.email')])) + emailRef.current.focus() + return false + } else { + // 이메일 정규식 검사 + if (!emailRegex.test(email)) { + alert(getMessage('join.validation.check1', [getMessage('join.sub2.email')])) + emailRef.current.focus() + return false + } + } + + // 담당자 정보 - 전화번호 + const userTelNo = formData.get('userTelNo') + if (!isObjectNotEmpty(userTelNo)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub2.telNo')])) + userTelNoRef.current.focus() + return false + } + + // 담당자 정보 - FAX 번호 + const userFax = formData.get('userFax') + if (!isObjectNotEmpty(userFax)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub2.fax')])) + userFaxRef.current.focus() + return false + } + return true + } + // 가입 신청 const joinProcess = async (e) => { e.preventDefault() const formData = new FormData(e.target) - const param = { - storeQcastNm: formData.get('storeQcastNm'), - storeQcastNmKana: formData.get('storeQcastNmKana'), - postCd: formData.get('postCd'), - addr: formData.get('addr'), - telNo: formData.get('telNo'), - fax: formData.get('fax'), - bizNo: formData.get('bizNo'), - userInfo: { - userId: formData.get('userId'), - userNm: formData.get('userNm'), - userNmKana: formData.get('userNmKana'), - telNo: formData.get('userTelNo'), - fax: formData.get('userFax'), - email: formData.get('email'), - category: formData.get('category'), - }, - } - - await promisePost({ url: '/api/login/v1.0/user/join', data: param }) - .then((res) => { - if (res) { - if (res.data.result.resultCode == 'S') { - Cookies.set('joinEmail', formData.get('email'), { expires: 1 }) - router.push('/join/complete') - } else { - alert(res.data.result.resultMsg) - } + if (joinValidation(formData)) { + if (confirm(getMessage('join.complete.save.confirm'))) { + const param = { + storeQcastNm: formData.get('storeQcastNm'), + storeQcastNmKana: formData.get('storeQcastNmKana'), + postCd: formData.get('postCd'), + addr: formData.get('addr'), + telNo: formData.get('telNo'), + fax: formData.get('fax'), + bizNo: formData.get('bizNo'), + userInfo: { + userId: formData.get('userId'), + userNm: formData.get('userNm'), + userNmKana: formData.get('userNmKana'), + telNo: formData.get('userTelNo'), + fax: formData.get('userFax'), + email: formData.get('email'), + category: formData.get('category'), + }, } - }) - .catch((error) => { - alert(error.response.data.message) - }) + + await promisePost({ url: '/api/login/v1.0/user/join', data: param }) + .then((res) => { + if (res) { + if (res.data.result.resultCode == 'S') { + Cookies.set('joinEmail', formData.get('email'), { expires: 1 }) + router.push('/join/complete') + } else { + alert(res.data.result.resultMsg) + } + } + }) + .catch((error) => { + alert(error.response.data.message) + }) + } + } } return ( @@ -71,6 +191,7 @@ export default function Join() { + {/* 판매대리점명 */} {getMessage('join.sub1.storeQcastNm')} * @@ -81,14 +202,16 @@ export default function Join() { type="text" id="storeQcastNm" name="storeQcastNm" - required alt={getMessage('join.sub1.storeQcastNm')} className="input-light" placeholder={getMessage('join.sub1.storeQcastNm_placeholder')} + maxLength={30} + ref={storeQcastNmRef} /> + {/* 판매대리점명 후리가나 */} {getMessage('join.sub1.storeQcastNmKana')} * @@ -99,13 +222,15 @@ export default function Join() { type="text" id="storeQcastNmKana" name="storeQcastNmKana" - required className="input-light" placeholder={getMessage('join.sub1.storeQcastNmKana_placeholder')} + maxLength={30} + ref={storeQcastNmKanaRef} /> + {/* 우편번호/주소 */} {getMessage('join.sub1.postCd')}/{getMessage('join.sub1.addr')} * @@ -117,9 +242,11 @@ export default function Join() { type="text" id="postCd" name="postCd" - required className="input-light" placeholder={getMessage('join.sub1.postCd_placeholder')} + onChange={inputNumberCheck} + maxLength={7} + ref={postCdRef} />
@@ -127,14 +254,16 @@ export default function Join() { type="text" id="addr" name="addr" - required className="input-light" placeholder={getMessage('join.sub1.addr_placeholder')} + maxLength={50} + ref={addrRef} />
+ {/* 전화번호 */} {getMessage('join.sub1.telNo')} * @@ -145,13 +274,16 @@ export default function Join() { type="text" id="telNo" name="telNo" - required className="input-light" placeholder={getMessage('join.sub1.telNo_placeholder')} - > + maxLength={15} + onChange={inputTelNumberCheck} + ref={telNoRef} + /> + {/* FAX 번호 */} {getMessage('join.sub1.fax')} * @@ -162,18 +294,21 @@ export default function Join() { type="text" id="fax" name="fax" - required className="input-light" placeholder={getMessage('join.sub1.fax_placeholder')} - > + maxLength={15} + onChange={inputTelNumberCheck} + ref={faxRef} + /> + {/* 법인번호 */} {getMessage('join.sub1.bizNo')}
- +
@@ -196,44 +331,49 @@ export default function Join() { + {/* 담당자명 */} {getMessage('join.sub2.userNm')} *
- +
+ {/* 담당자명 후리가나 */} {getMessage('join.sub2.userNmKana')}
- +
+ {/* 신청 ID */} {getMessage('join.sub2.userId')} *
- +
+ {/* 이메일 주소 */} {getMessage('join.sub2.email')} *
- +
+ {/* 전화번호 */} {getMessage('join.sub2.telNo')} * @@ -246,11 +386,14 @@ export default function Join() { name="userTelNo" className="input-light" placeholder={getMessage('join.sub2.telNo_placeholder')} - required + maxLength={15} + onChange={inputTelNumberCheck} + ref={userTelNoRef} /> + {/* FAX 번호 */} {getMessage('join.sub2.fax')} * @@ -263,16 +406,19 @@ export default function Join() { name="userFax" className="input-light" placeholder={getMessage('join.sub1.fax_placeholder')} - required + maxLength={15} + onChange={inputTelNumberCheck} + ref={userFaxRef} /> + {/* 부서명 */} {getMessage('join.sub2.category')}
- +
diff --git a/src/components/auth/Login.jsx b/src/components/auth/Login.jsx index 80b34f9f..ef4f92a3 100644 --- a/src/components/auth/Login.jsx +++ b/src/components/auth/Login.jsx @@ -331,7 +331,15 @@ export default function Login() { >
-
@@ -55,7 +58,7 @@ export default function BoardDetailModal({ noticeNo, setOpen }) { {boardDetail.listFile && (
-
첨부파일 목록
+
{getMessage('board.sub.fileList')}
{boardDetail.listFile.map((boardFile) => (
diff --git a/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx b/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx index ab8553a2..2b5a7079 100644 --- a/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx +++ b/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx @@ -11,12 +11,13 @@ export default function EavesGableEdit({ id, pos = { x: 50, y: 230 } }) { const { getMessage } = useMessage() const { closePopup } = usePopup() - const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef } = useEavesGableEdit(id) + const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef, pitchText } = useEavesGableEdit(id) const eavesProps = { pitchRef, offsetRef, widthRef, radioTypeRef, + pitchText, } const gableProps = { @@ -24,6 +25,7 @@ export default function EavesGableEdit({ id, pos = { x: 50, y: 230 } }) { offsetRef, widthRef, radioTypeRef, + pitchText, } const wallMergeProps = { diff --git a/src/components/floor-plan/modal/eavesGable/type/Eaves.jsx b/src/components/floor-plan/modal/eavesGable/type/Eaves.jsx index 80120107..a7d7d466 100644 --- a/src/components/floor-plan/modal/eavesGable/type/Eaves.jsx +++ b/src/components/floor-plan/modal/eavesGable/type/Eaves.jsx @@ -1,14 +1,17 @@ import { useMessage } from '@/hooks/useMessage' import Image from 'next/image' import { useState } from 'react' +import { useRecoilValue } from 'recoil' +import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom' -export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef }) { +export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) { const { getMessage } = useMessage() const [type, setType] = useState('1') const onChange = (e) => { setType(e.target.value) radioTypeRef.current = e.target.value } + const currentAngleType = useRecoilValue(currentAngleTypeSelector) return ( <>
@@ -17,9 +20,9 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef }) { {getMessage('slope')}
- +
- + {pitchText}
diff --git a/src/components/floor-plan/modal/eavesGable/type/Gable.jsx b/src/components/floor-plan/modal/eavesGable/type/Gable.jsx index 23f9c300..999687fd 100644 --- a/src/components/floor-plan/modal/eavesGable/type/Gable.jsx +++ b/src/components/floor-plan/modal/eavesGable/type/Gable.jsx @@ -1,14 +1,18 @@ import { useMessage } from '@/hooks/useMessage' import Image from 'next/image' import { useState } from 'react' +import { useRecoilValue } from 'recoil' +import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom' -export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef }) { +export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) { const { getMessage } = useMessage() const [type, setType] = useState('1') const onChange = (e) => { setType(e.target.value) radioTypeRef.current = e.target.value } + const currentAngleType = useRecoilValue(currentAngleTypeSelector) + return ( <>
@@ -57,9 +61,15 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef }) { {getMessage('slope')}
- +
- + {pitchText}
diff --git a/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx b/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx index ff749a6f..9fa412f9 100644 --- a/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx +++ b/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx @@ -1,24 +1,68 @@ import WithDraggable from '@/components/common/draggable/withDraggable' -import { useState } from 'react' +import { useEffect, useState } from 'react' import QSelectBox from '@/components/common/select/QSelectBox' import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { useMessage } from '@/hooks/useMessage' import { usePopup } from '@/hooks/usePopup' -const SelectOption01 = [{ name: 'M' }, { name: 'M' }, { name: 'M' }, { name: 'M' }] - export default function FlowDirectionSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) - const { id, pos = contextPopupPosition } = props + const { id, pos = contextPopupPosition, target } = props const { getMessage } = useMessage() const { closePopup } = usePopup() - const [compasDeg, setCompasDeg] = useState(0) + const [compasDeg, setCompasDeg] = useState(360) + const orientations = [ + { name: `${getMessage('commons.south')}`, value: 360 }, + { name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 }, + { name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: 45 }, + { name: `${getMessage('commons.east')}`, value: 270 }, + { name: `${getMessage('commons.west')}`, value: 90 }, + { name: `${getMessage('commons.north')}${getMessage('commons.east')}`, value: 225 }, + { name: `${getMessage('commons.north')}${getMessage('commons.west')}`, value: 135 }, + { name: `${getMessage('commons.north')}`, value: 180 }, + ] + const [selectedOrientation, setSelectedOrientation] = useState(orientations[0]) + const [type, setType] = useState('0') + useEffect(() => { + if (target?.angle === 0) { + setCompasDeg(360) + } else { + setCompasDeg(target?.angle ?? 360) + } + }, []) + useEffect(() => { + if (type === '0') { + setCompasDeg(selectedOrientation.value) + } + }, [selectedOrientation]) + + useEffect(() => { + if (type === '1') { + if ([15, 345, 360].includes(compasDeg)) { + setSelectedOrientation(orientations[0]) + } else if ([30, 45, 60].includes(compasDeg)) { + setSelectedOrientation(orientations[2]) + } else if ([75, 90, 105].includes(compasDeg)) { + setSelectedOrientation(orientations[4]) + } else if ([120, 135, 150].includes(compasDeg)) { + setSelectedOrientation(orientations[6]) + } else if ([165, 180, 195].includes(compasDeg)) { + setSelectedOrientation(orientations[7]) + } else if ([210, 225, 240].includes(compasDeg)) { + setSelectedOrientation(orientations[5]) + } else if ([255, 270, 285].includes(compasDeg)) { + setSelectedOrientation(orientations[3]) + } else if ([300, 315, 330].includes(compasDeg)) { + setSelectedOrientation(orientations[1]) + } + } + }, [compasDeg]) return (
-

面フローの設定

+

{getMessage('modal.shape.flow.direction.setting')}

@@ -26,113 +70,59 @@ export default function FlowDirectionSetting(props) {
-
流れ方向の設定
-
流れ方向を選択してください。
+
{getMessage('modal.flow.direction.setting')}
+
{getMessage('modal.flow.direction.setting.info')}
- - ドン - - 立つ + {getMessage('commons.north')} + {getMessage('commons.east')} + {getMessage('commons.south')} + {getMessage('commons.west')}
-
方位設定
-
シミュレーション計算の方向を指定します。面が向いている方位を選択してください。
+
{getMessage('modal.module.basic.setting.orientation.setting')}
+
{getMessage('modal.shape.flow.direction.setting.orientation.setting.info')}
- - + setType(e.target.value)} /> +
- + setSelectedOrientation(e)} />
- - + setType(e.target.value)} /> +
-
setCompasDeg(180)}> - 13 -
-
setCompasDeg(195)}> - 12 -
-
setCompasDeg(210)}> - 11 -
-
setCompasDeg(225)}> - 10 -
-
setCompasDeg(240)}> - 9 -
-
setCompasDeg(255)}> - 8 -
-
setCompasDeg(270)}> - 7 -
-
setCompasDeg(285)}> - 6 -
-
setCompasDeg(300)}> - 5 -
-
setCompasDeg(315)}> - 4 -
-
setCompasDeg(330)}> - 3 -
-
setCompasDeg(345)}> - 2 -
-
setCompasDeg(0)}> - 1 -
-
setCompasDeg(15)}> - 24 -
-
setCompasDeg(30)}> - 23 -
-
setCompasDeg(45)}> - 22 -
-
setCompasDeg(60)}> - 21 -
-
setCompasDeg(75)}> - 20 -
-
setCompasDeg(90)}> - 19 -
-
setCompasDeg(105)}> - 18 -
-
setCompasDeg(120)}> - 17 -
-
setCompasDeg(135)}> - 16 -
-
setCompasDeg(150)}> - 15 -
-
setCompasDeg(165)}> - 14 -
+ {Array.from({ length: 180 / 15 + 1 }).map((dot, index) => ( +
setCompasDeg(15 * (12 + index))} + > + {13 - index} +
+ ))} + {Array.from({ length: 180 / 15 - 1 }).map((dot, index) => ( +
setCompasDeg(15 * (index + 1))} + > + {24 - index} +
+ ))}
@@ -141,7 +131,7 @@ export default function FlowDirectionSetting(props) {
- +
diff --git a/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx b/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx index 16871a19..f5dc4b46 100644 --- a/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx +++ b/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx @@ -3,99 +3,70 @@ import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { useMessage } from '@/hooks/useMessage' import { usePopup } from '@/hooks/usePopup' +import { useState } from 'react' export default function LinePropertySetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition } = props const { getMessage } = useMessage() const { closePopup } = usePopup() + const properties = [ + { name: getMessage('eaves.line'), value: 'eaves' }, + { name: getMessage('ridge'), value: 'ridge' }, + { name: getMessage('oneside.flow.ridge'), value: 'onesideFlowRidge' }, + { name: getMessage('gable'), value: 'gable' }, + { name: getMessage('gable.left'), value: 'gableLeft' }, + { name: getMessage('gable.right'), value: 'gableRight' }, + { name: getMessage('yosemune'), value: 'yosemune' }, + { name: getMessage('valley'), value: 'valley' }, + { name: getMessage('l.abandon.valley'), value: 'lAbandonValley' }, + { name: getMessage('mansard'), value: 'mansard' }, + { name: getMessage('wall.merge'), value: 'wallCollection' }, + { name: getMessage('wall.merge.type'), value: 'wallCollectionType' }, + { name: getMessage('wall.merge.flow'), value: 'wallCollectionFlow' }, + { name: getMessage('wall.merge.flow.left'), value: 'wallCollectionFlowLeft' }, + { name: getMessage('wall.merge.flow.right'), value: 'wallCollectionFlowRight' }, + { name: getMessage('no.setting'), value: 'noSetting' }, + ] + const [selectedProperty, setSelectedProperty] = useState(null) return ( -
+
-

各辺属性の変更

+

{getMessage('contextmenu.line.property.edit')}

- 属性を変更する辺を選択してください。 - 選択した値 [龍丸] + {getMessage('modal.line.property.edit.info')} + + {getMessage('modal.line.property.edit.selected')} [ {selectedProperty?.name} ] +
-
設定
+
{getMessage('setting')}
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+ {properties.map((property, index) => { + return ( +
+ = 10 ? index + 1 : `0${index + 1}`)} + onChange={(e) => setSelectedProperty(property)} + /> + +
+ ) + })}
- +
diff --git a/src/components/floor-plan/modal/object/DormerOffset.jsx b/src/components/floor-plan/modal/object/DormerOffset.jsx index 442e92df..b4292821 100644 --- a/src/components/floor-plan/modal/object/DormerOffset.jsx +++ b/src/components/floor-plan/modal/object/DormerOffset.jsx @@ -13,26 +13,26 @@ export default function DormerOffset(props) {
-

도머 오프셋

+

{getMessage('contextmenu.dormer.offset')}

-
移動する方向を入力してください
+
{getMessage('modal.dormer.offset.info')}
-

長さ

+

{getMessage('length')}

- +
mm
- +
mm
@@ -46,7 +46,7 @@ export default function DormerOffset(props) {
- +
diff --git a/src/components/floor-plan/modal/object/SizeSetting.jsx b/src/components/floor-plan/modal/object/SizeSetting.jsx index ca7d4773..bdb25821 100644 --- a/src/components/floor-plan/modal/object/SizeSetting.jsx +++ b/src/components/floor-plan/modal/object/SizeSetting.jsx @@ -2,7 +2,6 @@ import { useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' -import { canvasState } from '@/store/canvasAtom' import WithDraggable from '@/components/common/draggable/WithDraggable' import { usePopup } from '@/hooks/usePopup' import { contextPopupPositionState } from '@/store/popupAtom' @@ -11,16 +10,15 @@ import { useState } from 'react' export default function SizeSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const [settingTarget, setSettingTarget] = useState(1) - const { id, pos = contextPopupPosition } = props + const { id, pos = contextPopupPosition, target } = props const { getMessage } = useMessage() - const canvas = useRecoilValue(canvasState) const { closePopup } = usePopup() return (
-

サイズ変更

+

{getMessage('modal.size.setting')}

@@ -30,11 +28,11 @@ export default function SizeSetting(props) {
- + mm
- + mm
@@ -43,11 +41,11 @@ export default function SizeSetting(props) {
- + mm
- + mm
@@ -62,7 +60,7 @@ export default function SizeSetting(props) {
- +
diff --git a/src/components/floor-plan/modal/panelBatch/PanelBatchStatistics.jsx b/src/components/floor-plan/modal/panelBatch/PanelBatchStatistics.jsx new file mode 100644 index 00000000..2d36c4eb --- /dev/null +++ b/src/components/floor-plan/modal/panelBatch/PanelBatchStatistics.jsx @@ -0,0 +1,37 @@ +import WithDraggable from '@/components/common/draggable/withDraggable' +import { useState } from 'react' +import { useMessage } from '@/hooks/useMessage' + +export default function PanelBatchStatistics() { + const { getMessage } = useMessage() + const [isFold, setIsFold] = useState(false) + const [pos, setPos] = useState({ + x: 0, + y: 30, + }) + + return ( + +
+

{getMessage('modal.panel.batch.statistic')}

+ +
+ + + + + + + + + + + + + +
{getMessage('modal.panel.batch.statistic.roof.shape')}{getMessage('modal.panel.batch.statistic.power.generation.amount')} (kW)
{getMessage('modal.panel.batch.statistic.total')}0.000
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 79a89b88..b635ea86 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -8,6 +8,7 @@ import { useMessage } from '@/hooks/useMessage' import { useAxios } from '@/hooks/useAxios' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' +import { basicSettingState } from '@/store/settingAtom' export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) { const [objectNo, setObjectNo] = useState('test123241008001') // 후에 삭제 필요 @@ -16,22 +17,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(1) const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) const { closePopup } = usePopup() - const [basicSetting, setBasicSettings] = useState({ - roofSizeSet: 1, - roofAngleSet: 'slope', - roofs: [ - { - roofApply: true, - roofSeq: 1, - roofType: 1, - roofWidth: 200, - roofHeight: 200, - roofHajebichi: 200, - roofGap: 0, - roofLayout: 'parallel', - }, - ], - }) + const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) const { getMessage } = useMessage() const { get, post } = useAxios() diff --git a/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx b/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx index b9ddd97d..090eec0a 100644 --- a/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx +++ b/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx @@ -7,18 +7,21 @@ import { useRoofShapePassivitySetting } from '@/hooks/roofcover/useRoofShapePass import { usePopup } from '@/hooks/usePopup' export default function RoofShapePassivitySetting({ id, pos = { x: 50, y: 230 } }) { - const { handleSave, handleConfirm, handleRollback, buttons, type, setType, TYPES, offsetRef, pitchRef } = useRoofShapePassivitySetting(id) + const { handleSave, handleConfirm, handleRollback, buttons, type, setType, TYPES, offsetRef, pitchRef, pitchText } = + useRoofShapePassivitySetting(id) const { getMessage } = useMessage() const { closePopup } = usePopup() const eavesProps = { offsetRef, pitchRef, + pitchText, } const gableProps = { offsetRef, pitchRef, + pitchText, } const shedProps = { diff --git a/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx b/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx index 48058b1b..e60aadc6 100644 --- a/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx +++ b/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx @@ -38,11 +38,12 @@ export default function RoofShapeSetting({ id, pos = { x: 50, y: 230 } }) { buttonMenu, handleConfirm, handleRollBack, + pitchText, } = useRoofShapeSetting(id) const { closePopup } = usePopup() - const ridgeProps = { pitch, setPitch, eavesOffset, setEavesOffset } - const patternProps = { pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset } + const ridgeProps = { pitch, setPitch, eavesOffset, setEavesOffset, pitchText } + const patternProps = { pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset, pitchText } const sideProps = { pitch, setPitch, @@ -67,6 +68,7 @@ export default function RoofShapeSetting({ id, pos = { x: 50, y: 230 } }) { buttonMenu, handleConfirm, handleRollBack, + pitchText, } const directionProps = { @@ -78,6 +80,7 @@ export default function RoofShapeSetting({ id, pos = { x: 50, y: 230 } }) { setGableOffset, shedWidth, setShedWidth, + pitchText, } return ( diff --git a/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx b/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx index 3a16b5c0..2ba0f3e3 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx @@ -1,7 +1,10 @@ import { useMessage } from '@/hooks/useMessage' +import { useRecoilValue } from 'recoil' +import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom' -export default function Eaves({ offsetRef, pitchRef }) { +export default function Eaves({ offsetRef, pitchRef, pitchText }) { const { getMessage } = useMessage() + const currentAngleType = useRecoilValue(currentAngleTypeSelector) return ( <>
@@ -9,9 +12,9 @@ export default function Eaves({ offsetRef, pitchRef }) { {getMessage('slope')}
- +
- + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx b/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx index 69b2cf9d..e7d9718d 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx @@ -1,7 +1,10 @@ import { useMessage } from '@/hooks/useMessage' +import { useRecoilValue } from 'recoil' +import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom' -export default function Gable({ offsetRef, pitchRef }) { +export default function Gable({ offsetRef, pitchRef, pitchText }) { const { getMessage } = useMessage() + const currentAngleType = useRecoilValue(currentAngleTypeSelector) return ( <>
@@ -9,9 +12,9 @@ export default function Gable({ offsetRef, pitchRef }) { {getMessage('slope')}
- +
- + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/type/Direction.jsx b/src/components/floor-plan/modal/roofShape/type/Direction.jsx index 51faffcb..ef2be366 100644 --- a/src/components/floor-plan/modal/roofShape/type/Direction.jsx +++ b/src/components/floor-plan/modal/roofShape/type/Direction.jsx @@ -1,7 +1,7 @@ import { useMessage } from '@/hooks/useMessage' import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils' -export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset, shedWidth, setShedWidth }) { +export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset, shedWidth, setShedWidth, pitchText }) { const { getMessage } = useMessage() return (
@@ -12,7 +12,7 @@ export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset
onlyNumberWithDotInputChange(e, setPitch)} />
- {getMessage('size')} + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/type/Pattern.jsx b/src/components/floor-plan/modal/roofShape/type/Pattern.jsx index 0c086fbe..704b3a52 100644 --- a/src/components/floor-plan/modal/roofShape/type/Pattern.jsx +++ b/src/components/floor-plan/modal/roofShape/type/Pattern.jsx @@ -3,7 +3,7 @@ import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/inpu export default function Pattern(props) { const { getMessage } = useMessage() - const { pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset } = props + const { pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset, pitchText } = props return (
@@ -13,7 +13,7 @@ export default function Pattern(props) {
onlyNumberWithDotInputChange(e, setPitch)} />
- {getMessage('size')} + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/type/Ridge.jsx b/src/components/floor-plan/modal/roofShape/type/Ridge.jsx index 13dab6f3..538cda3d 100644 --- a/src/components/floor-plan/modal/roofShape/type/Ridge.jsx +++ b/src/components/floor-plan/modal/roofShape/type/Ridge.jsx @@ -4,7 +4,7 @@ import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/inpu export default function Ridge(props) { const { getMessage } = useMessage() - const { pitch, setPitch, eavesOffset, setEavesOffset } = props + const { pitch, setPitch, eavesOffset, setEavesOffset, pitchText } = props return (
@@ -15,7 +15,7 @@ export default function Ridge(props) {
onlyNumberWithDotInputChange(e, setPitch)} />
- {getMessage('size')} + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/type/Side.jsx b/src/components/floor-plan/modal/roofShape/type/Side.jsx index b2f032ba..487ccd43 100644 --- a/src/components/floor-plan/modal/roofShape/type/Side.jsx +++ b/src/components/floor-plan/modal/roofShape/type/Side.jsx @@ -32,13 +32,14 @@ export default function Side(props) { buttonMenu, handleConfirm, handleRollBack, + pitchText, } = props - const eavesProps = { pitch, setPitch, eavesOffset, setEavesOffset } + const eavesProps = { pitch, setPitch, eavesOffset, setEavesOffset, pitchText } const gableProps = { gableOffset, setGableOffset } const wallProps = { sleeveOffset, setSleeveOffset, hasSleeve, setHasSleeve } - const hipAndGableProps = { pitch, setPitch, eavesOffset, setEavesOffset, hipAndGableWidth, setHipAndGableWidth } - const jerkinheadProps = { gableOffset, setGableOffset, jerkinHeadWidth, setJerkinHeadWidth, jerkinHeadPitch, setJerkinHeadPitch } + const hipAndGableProps = { pitch, setPitch, eavesOffset, setEavesOffset, hipAndGableWidth, setHipAndGableWidth, pitchText } + const jerkinheadProps = { gableOffset, setGableOffset, jerkinHeadWidth, setJerkinHeadWidth, jerkinHeadPitch, setJerkinHeadPitch, pitchText } const shedProps = { shedWidth, setShedWidth } const { getMessage } = useMessage() diff --git a/src/components/floor-plan/modal/roofShape/type/option/Eaves.jsx b/src/components/floor-plan/modal/roofShape/type/option/Eaves.jsx index 8d061a7b..cd39a439 100644 --- a/src/components/floor-plan/modal/roofShape/type/option/Eaves.jsx +++ b/src/components/floor-plan/modal/roofShape/type/option/Eaves.jsx @@ -1,7 +1,7 @@ import { useMessage } from '@/hooks/useMessage' import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils' -export default function Eaves({ pitch, setPitch, eavesOffset, setEavesOffset }) { +export default function Eaves({ pitch, setPitch, eavesOffset, setEavesOffset, pitchText }) { const { getMessage } = useMessage() return ( <> @@ -12,7 +12,7 @@ export default function Eaves({ pitch, setPitch, eavesOffset, setEavesOffset })
onlyNumberWithDotInputChange(e, setPitch)} />
- {getMessage('size')} + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/type/option/HipAndGable.jsx b/src/components/floor-plan/modal/roofShape/type/option/HipAndGable.jsx index ce4a57cc..a59e94b1 100644 --- a/src/components/floor-plan/modal/roofShape/type/option/HipAndGable.jsx +++ b/src/components/floor-plan/modal/roofShape/type/option/HipAndGable.jsx @@ -1,7 +1,7 @@ import { useMessage } from '@/hooks/useMessage' import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils' -export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffset, hipAndGableWidth, setHipAndGableWidth }) { +export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffset, hipAndGableWidth, setHipAndGableWidth, pitchText }) { const { getMessage } = useMessage() return ( <> @@ -12,7 +12,7 @@ export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffs
onlyNumberWithDotInputChange(e, setPitch)} />
- {getMessage('size')} + {pitchText}
diff --git a/src/components/floor-plan/modal/roofShape/type/option/Jerkinhead.jsx b/src/components/floor-plan/modal/roofShape/type/option/Jerkinhead.jsx index be846e94..e1b822e7 100644 --- a/src/components/floor-plan/modal/roofShape/type/option/Jerkinhead.jsx +++ b/src/components/floor-plan/modal/roofShape/type/option/Jerkinhead.jsx @@ -1,7 +1,15 @@ import { useMessage } from '@/hooks/useMessage' import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils' -export default function Jerkinhead({ gableOffset, setGableOffset, jerkinHeadWidth, setJerkinHeadWidth, jerkinHeadPitch, setJerkinHeadPitch }) { +export default function Jerkinhead({ + gableOffset, + setGableOffset, + jerkinHeadWidth, + setJerkinHeadWidth, + jerkinHeadPitch, + setJerkinHeadPitch, + pitchText, +}) { const { getMessage } = useMessage() return ( <> @@ -35,7 +43,7 @@ export default function Jerkinhead({ gableOffset, setGableOffset, jerkinHeadWidt onChange={(e) => onlyNumberWithDotInputChange(e, setJerkinHeadPitch)} />
- {getMessage('size')} + {pitchText}
) diff --git a/src/components/main/ChangePasswordPop.jsx b/src/components/main/ChangePasswordPop.jsx index b5a3f8da..42ab6ef6 100644 --- a/src/components/main/ChangePasswordPop.jsx +++ b/src/components/main/ChangePasswordPop.jsx @@ -62,7 +62,7 @@ export default function ChangePasswordPop() { //패스워드 길이수 체크 if (checkLength(_password1) > 10) { - alert(getMessage('main.popup.login.validate2')) + return alert(getMessage('main.popup.login.validate2')) } const param = { @@ -81,6 +81,8 @@ export default function ChangePasswordPop() { } else { alert(res.result.resultMsg) } + } else { + console.log('error') } }) } diff --git a/src/components/main/MainContents.jsx b/src/components/main/MainContents.jsx index d2e38853..9490a868 100644 --- a/src/components/main/MainContents.jsx +++ b/src/components/main/MainContents.jsx @@ -9,6 +9,7 @@ import { useRouter } from 'next/navigation' import { globalLocaleStore } from '@/store/localeAtom' import { queryStringFormatter } from '@/util/common-utils' import { sessionStore } from '@/store/commonAtom' +import MainSkeleton from '../ui/MainSkeleton' export default function MainContents() { const { getMessage } = useMessage() @@ -109,30 +110,34 @@ export default function MainContents() {
-
    - {objectList.map((row) => { - return ( -
  • { - if (row.objectNo.substring(0, 1) === 'R') { - router.push(`/management/stuff/detail?objectNo=${row.objectNo.toString()}`) - } else { - router.push(`/management/stuff/tempdetail?objectNo=${row.objectNo.toString()}`) - } - }} - > -
    - {dayjs(row.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')} - {row.objectNo} - {row.objectName} - {row.saleStoreName} -
    -
  • - ) - })} -
+ {objectList.length > 0 ? ( +
    + {objectList.map((row) => { + return ( +
  • { + if (row.objectNo.substring(0, 1) === 'R') { + router.push(`/management/stuff/detail?objectNo=${row.objectNo.toString()}`) + } else { + router.push(`/management/stuff/tempdetail?objectNo=${row.objectNo.toString()}`) + } + }} + > +
    + {dayjs(row.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')} + {row.objectNo} + {row.objectName} + {row.saleStoreName} +
    +
  • + ) + })} +
+ ) : ( + + )}
@@ -142,7 +147,9 @@ export default function MainContents() {
{recentNoticeList[0]?.title}
{recentNoticeList[0]?.contents}
- ) : null} + ) : ( + + )}
@@ -163,7 +170,9 @@ export default function MainContents() { ) })} - ) : null} + ) : ( + + )} diff --git a/src/components/management/Stuff.jsx b/src/components/management/Stuff.jsx index 5771543f..138a3b85 100644 --- a/src/components/management/Stuff.jsx +++ b/src/components/management/Stuff.jsx @@ -33,8 +33,8 @@ export default function Stuff() { const { get } = useAxios(globalLocaleState) const gridRef = useRef() - const [selectedRowData, setSelectedRowData] = useState([]) - const [selectedRowDataCount, setSelectedRowDataCount] = useState(0) + // const [selectedRowData, setSelectedRowData] = useState([]) + // const [selectedRowDataCount, setSelectedRowDataCount] = useState(0) const router = useRouter() const pathname = usePathname() @@ -67,10 +67,10 @@ export default function Stuff() { field: 'lastEditDatetime', minWidth: 200, headerName: getMessage('stuff.gridHeader.lastEditDatetime'), - headerCheckboxSelection: true, - headerCheckboxSelectionCurrentPageOnly: true, //페이징시 현재 페이지만 체크되도록 - checkboxSelection: true, - showDisabledCheckboxes: true, + // headerCheckboxSelection: true, + // headerCheckboxSelectionCurrentPageOnly: true, //페이징시 현재 페이지만 체크되도록 + // checkboxSelection: true, + // showDisabledCheckboxes: true, cellStyle: { textAlign: 'center' }, valueFormatter: function (params) { if (params.value) { @@ -165,11 +165,11 @@ export default function Stuff() { } } - //그리드 체크박스 선택시 - const getSelectedRowdata = (data) => { - setSelectedRowData(data) - setSelectedRowDataCount(data.length) - } + //그리드 체크박스 선택시 미사용 + // const getSelectedRowdata = (data) => { + // setSelectedRowData(data) + // setSelectedRowDataCount(data.length) + // } //물건삭제 // const fnDeleteRowData = (data) => { @@ -405,8 +405,8 @@ export default function Stuff() { {convertNumberToPriceDecimal(totalCount)}
  • - {getMessage('stuff.search.grid.selected')} - {convertNumberToPriceDecimal(selectedRowDataCount)} + {/* {getMessage('stuff.search.grid.selected')} */} + {/* {convertNumberToPriceDecimal(selectedRowDataCount)} */}
  • @@ -428,7 +428,7 @@ export default function Stuff() {
    - +
    diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index b96818dc..4b15c8a0 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -3,7 +3,7 @@ import React, { useState, useEffect, useRef } from 'react' import { useRouter, useSearchParams, usePathname } from 'next/navigation' import { Button } from '@nextui-org/react' -import Select from 'react-select' +import Select, { components } from 'react-select' import Link from 'next/link' import { useAxios } from '@/hooks/useAxios' import { globalLocaleStore } from '@/store/localeAtom' @@ -19,6 +19,13 @@ import { useCommonCode } from '@/hooks/common/useCommonCode' import StuffPlanQGrid from './StuffPlanQGrid' export default function StuffDetail() { + const inputReceiveUserEl = useRef(null) //담당자ref + const inputObjectNameEl = useRef(null) //물건명ref + const inputZipNoEl = useRef(null) //우편번호ref + const inputAddressEl = useRef(null) //주소ref + const inputVerticalSnowCoverEl = useRef(null) //수직적설량ref + const inputInstallHeightEl = useRef(null) //설치높이ref + //공통코드 const { commonCode, findCommonCode } = useCommonCode() const [selOptions, setSelOptions] = useState('') //선택한 1차점 @@ -54,7 +61,7 @@ export default function StuffDetail() { address: '', //주소 areaId: '', //발전량시뮬레이션지역id standardWindSpeedId: '', //기준풍속 - verticalSnowCover: '', //수직적설량NEW + verticalSnowCover: '', //수직적설량 coldRegionFlg: false, //한랭지대책시행(true : 1 / false : 0) surfaceType: 'III・IV', //면조도구분(III・IV / Ⅱ) saltAreaFlg: false, //염해지역용아이템사용 (true : 1 / false : 0) @@ -427,7 +434,6 @@ export default function StuffDetail() { //도도부현 / 주소 setPrefValue(detailData.prefId) form.setValue('prefId', detailData.prefId) - //prefName ??? form.setValue('address', detailData.address) //발전시뮬 form.setValue('areaId', detailData.areaId) @@ -439,7 +445,7 @@ export default function StuffDetail() { //한랭지대책시행 coldRegionFlg 1이면 true form.setValue('coldRegionFlg', detailData.coldRegionFlg === '1' ? true : false) - //면조도구분surfaceType + //면조도구분 surfaceType null로 내려오면 셋팅 안하고 저장할때 필수값 체크하도록 // form.setValue('surfaceType', 'Ⅱ') // form.setValue('surfaceType', 'III・IV') form.setValue('surfaceType', detailData.surfaceType) @@ -447,8 +453,12 @@ export default function StuffDetail() { form.setValue('saltAreaFlg', detailData.saltAreaFlg === '1' ? true : false) //설치높이 form.setValue('installHeight', detailData.installHeight) - //계약조건 - form.setValue('conType', detailData.conType) + //계약조건 null로 내려오면 0으로 디폴트셋팅 + if (detailData.conType === null) { + form.setValue('conType', '0') + } else { + form.setValue('conType', detailData.conType) + } //메모 form.setValue('remarks', detailData.remarks) }) @@ -481,8 +491,10 @@ export default function StuffDetail() { } //1차점 변경 이벤트 const onSelectionChange = (key) => { - if (key.saleStoreId === selOptions) { - return + if (isObjectNotEmpty(key)) { + if (key.saleStoreId === selOptions) { + return + } } const planReqNo = form.watch('planReqNo') @@ -593,8 +605,10 @@ export default function StuffDetail() { //2차점 변경 이벤트 const onSelectionChange2 = (key) => { - if (key.saleStoreId === otherSelOptions) { - return + if (isObjectNotEmpty(key)) { + if (key.saleStoreId === otherSelOptions) { + return + } } const planReqNo = form.watch('planReqNo') @@ -883,56 +897,78 @@ export default function StuffDetail() { form.setValue('areaName', e.prefName) } + // 저장 const onValid = async () => { const formData = form.getValues() let errors = {} let fieldNm + //담당자 if (!formData.receiveUser || formData.receiveUser.trim().length === 0) { fieldNm = getMessage('stuff.detail.receiveUser') errors = fieldNm + inputReceiveUserEl.current.focus() } + //물건명 if (!formData.objectName || formData.objectName.trim().length === 0) { fieldNm = getMessage('stuff.detail.objectStatusId') errors = fieldNm + inputObjectNameEl.current.focus() } + //경칭 if (!formData.objectNameOmit) { fieldNm = getMessage('stuff.detail.objectNameOmit') errors = fieldNm } + //1차판매점명 if (!formData.saleStoreId) { fieldNm = getMessage('stuff.detail.saleStoreId') errors = fieldNm } - + //우편번호 if (!formData.zipNo) { fieldNm = getMessage('stuff.detail.zipNo') errors = fieldNm + inputZipNoEl.current.focus() } - + //주소 + if (!formData.address) { + fieldNm = getMessage('stuff.detail.address') + errors = fieldNm + inputAddressEl.current.focus() + } + //도도부현 if (!formData.prefId || formData.prefId === '0') { fieldNm = getMessage('stuff.detail.prefId') errors = fieldNm } - + //발전시뮬레이션지역 if (!formData.areaId) { fieldNm = getMessage('stuff.detail.areaId') errors = fieldNm } - + //기준풍속 if (!formData.standardWindSpeedId) { fieldNm = getMessage('stuff.detail.standardWindSpeedId') errors = fieldNm } - + //수직적설량 if (!formData.verticalSnowCover) { fieldNm = getMessage('stuff.detail.verticalSnowCover') errors = fieldNm + inputVerticalSnowCoverEl.current.focus() } + //면조도구분 + if (!formData.surfaceType) { + fieldNm = getMessage('stuff.detail.surfaceType') + errors = fieldNm + } + //설치높이 if (!formData.installHeight) { fieldNm = getMessage('stuff.detail.installHeight') errors = fieldNm + inputInstallHeightEl.current.focus() } if (Object.keys(errors).length > 0) { @@ -1101,6 +1137,7 @@ export default function StuffDetail() { if (height === '0') { return alert(getMessage('stuff.detail.save.valierror2')) } + await promisePost({ url: '/api/object/save-object', data: params }).then((res) => { if (res.status === 201) { alert(getMessage('stuff.detail.tempSave.message1')) @@ -1129,6 +1166,17 @@ export default function StuffDetail() { input.value = input.value.replace(/[^0-9]/g, '') } + //자동완성 옵션 없을때 메세지 컴포넌트.. + const NoOptionsMessage = (props) => { + return ( + + + TEXTTTTTTT + + + ) + } + return ( <> {(editMode === 'NEW' && ( @@ -1162,7 +1210,7 @@ export default function StuffDetail() { )) || null}
    -
    @@ -1174,7 +1222,7 @@ export default function StuffDetail() {
    - +
    @@ -1203,7 +1251,7 @@ export default function StuffDetail() { })} {/* 라디오끝 */}
    - +
    - +
    -
    {getMessage('stuff.detail.btn.addressPop.guide')}
    @@ -1344,7 +1393,7 @@ export default function StuffDetail() {
    -
    +
    {prefCodeList?.length > 0 && ( +
    @@ -1420,7 +1475,7 @@ export default function StuffDetail() { >
    {getMessage('stuff.detail.standardWindSpeedIdSpan')} -
    @@ -1439,6 +1494,7 @@ export default function StuffDetail() { onKeyUp={handleKeyUp} value={form.watch('verticalSnowCover') || ''} {...register('verticalSnowCover')} + ref={inputVerticalSnowCoverEl} />
    cm @@ -1486,6 +1542,7 @@ export default function StuffDetail() { onKeyUp={handleKeyUp} value={form.watch('installHeight') || ''} {...register('installHeight')} + ref={inputInstallHeightEl} />
    m @@ -1520,7 +1577,7 @@ export default function StuffDetail() {
    {!isFormValid ? ( - ) : ( @@ -1571,7 +1628,7 @@ export default function StuffDetail() { {/* {detailData?.tempFlg === '1' ? ( */} {objectNo.substring(0, 1) === 'T' ? ( <> - @@ -1585,7 +1642,13 @@ export default function StuffDetail() {
    - +
    @@ -1614,7 +1677,7 @@ export default function StuffDetail() { })} {/* 상세라디오끝 */}
    - +
    +
    -
    {getMessage('stuff.detail.btn.addressPop.guide')}
    @@ -1756,7 +1819,7 @@ export default function StuffDetail() {
    -
    +
    {prefCodeList?.length > 0 && ( +
    @@ -1836,7 +1905,7 @@ export default function StuffDetail() { >
    {getMessage('stuff.detail.standardWindSpeedIdSpan')} -
    @@ -1857,6 +1926,7 @@ export default function StuffDetail() { onKeyUp={handleKeyUp} value={form.watch('verticalSnowCover') || ''} {...register('verticalSnowCover')} + ref={inputVerticalSnowCoverEl} />
    cm @@ -1908,6 +1978,7 @@ export default function StuffDetail() { onKeyUp={handleKeyUp} value={form.watch('installHeight') || ''} {...register('installHeight')} + ref={inputInstallHeightEl} />
    m @@ -1981,13 +2052,13 @@ export default function StuffDetail() {
    -
    @@ -1996,17 +2067,17 @@ export default function StuffDetail() { <>
    {!isFormValid ? ( - ) : ( )}
    diff --git a/src/components/management/StuffHeader.jsx b/src/components/management/StuffHeader.jsx index d8cade80..d7e3246d 100644 --- a/src/components/management/StuffHeader.jsx +++ b/src/components/management/StuffHeader.jsx @@ -6,6 +6,7 @@ import { useRouter, useSearchParams } from 'next/navigation' import { globalLocaleStore } from '@/store/localeAtom' import { useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' +import dayjs from 'dayjs' export default function StuffHeader() { const { getMessage } = useMessage() @@ -57,11 +58,17 @@ export default function StuffHeader() {
    {getMessage('stuff.detail.header.lastEditDatetime')}
    -
    {headerData.lastEditDatetime}
    +
    + {headerData?.lastEditDatetime ? `${dayjs(headerData.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}` : ''}{' '} + {headerData?.lastEditUserName ? `(${headerData.lastEditUserName})` : null} +
    {getMessage('stuff.detail.header.createDatetime')}
    -
    {headerData.createDatetime}
    +
    + {headerData?.createDatetime ? `${dayjs(headerData.lastEditDatetime).format('YYYY.MM.DD')}` : ''}{' '} + {headerData?.createUserName ? `(${headerData.createUserName})` : null} +
    ) diff --git a/src/components/management/StuffPlanQGrid.jsx b/src/components/management/StuffPlanQGrid.jsx index 108e17a2..1c8b88ba 100644 --- a/src/components/management/StuffPlanQGrid.jsx +++ b/src/components/management/StuffPlanQGrid.jsx @@ -6,7 +6,7 @@ export default function StuffPlanQGrid(props) { const { planGridData, planGridColumns, isPageable = true } = props const [rowData, setRowData] = useState(null) - const [gridApi, setGridApi] = useState(null) + // const [gridApi, setGridApi] = useState(null) const [colDefs, setColDefs] = useState(planGridColumns) const defaultColDef = useMemo(() => { @@ -20,24 +20,24 @@ export default function StuffPlanQGrid(props) { } }, []) - const rowBuffer = 100 + const rowBuffer = 10 useEffect(() => { planGridData ? setRowData(planGridData) : '' }, [planGridData]) - const onGridReady = useCallback( - (params) => { - setGridApi(params.api) - planGridData ? setRowData(planGridData) : '' - }, - [planGridData], - ) + // const onGridReady = useCallback( + // (params) => { + // setGridApi(params.api) + // planGridData ? setRowData(planGridData) : '' + // }, + // [planGridData], + // ) return (
    { - props.getSelectedRowdata(event.api.getSelectedRows()) - }, []) + // 체크박스 체크시 체크박스 미사용 + // const onSelectionChanged = useCallback((event) => { + // props.getSelectedRowdata(event.api.getSelectedRows()) + // }, []) //더블클릭 const onCellDoubleClicked = useCallback((event) => { @@ -92,10 +95,10 @@ export default function StuffQGrid(props) { isRowSelectable={isRowSelectable} rowSelection={'multiple'} suppressRowClickSelection={true} - onSelectionChanged={onSelectionChanged} + // onSelectionChanged={onSelectionChanged} onCellDoubleClicked={onCellDoubleClicked} pagination={isPageable} - overlayNoRowsTemplate={'물건 목록이 없습니다.'} + overlayNoRowsTemplate={`${getMessage('stuff.grid.noData')}`} getRowClass={getRowClass} autoSizeAllColumns={true} /> diff --git a/src/components/management/popup/FindAddressPopQGrid.jsx b/src/components/management/popup/FindAddressPopQGrid.jsx index b9d1bb90..ba703625 100644 --- a/src/components/management/popup/FindAddressPopQGrid.jsx +++ b/src/components/management/popup/FindAddressPopQGrid.jsx @@ -1,11 +1,14 @@ import React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { AgGridReact } from 'ag-grid-react' +import { useMessage } from '@/hooks/useMessage' import 'ag-grid-community/styles/ag-grid.css' import 'ag-grid-community/styles/ag-theme-quartz.css' export default function FindAddressPopGrid(props) { + const { getMessage } = useMessage() + const { gridData, gridColumns, isPageable = true } = props const [rowData, setRowData] = useState(null) @@ -25,7 +28,7 @@ export default function FindAddressPopGrid(props) { } }, []) - const rowBuffer = 100 + const rowBuffer = 10 useEffect(() => { gridData ? setRowData(gridData) : '' @@ -46,7 +49,7 @@ export default function FindAddressPopGrid(props) { } return ( -
    +
    ${getMessage('stuff.grid.noData')}`} />
    ) diff --git a/src/components/management/popup/PlanRequestPop.jsx b/src/components/management/popup/PlanRequestPop.jsx index cde86d6b..bbe0f6c9 100644 --- a/src/components/management/popup/PlanRequestPop.jsx +++ b/src/components/management/popup/PlanRequestPop.jsx @@ -253,6 +253,12 @@ export default function PlanRequestPop(props) { } }, [commonCode]) + // 숫자만 입력 가능 + const handleKeyUp = (e) => { + let input = e.target + input.value = input.value.replace(/[^0-9]/g, '') + } + return (
    @@ -301,6 +307,7 @@ export default function PlanRequestPop(props) { type="text" className="input-light" value={schPlanReqNo} + onKeyUp={handleKeyUp} onChange={(e) => { setSchPlanReqNo(e.target.value) }} diff --git a/src/components/management/popup/PlanRequestPopQGrid.jsx b/src/components/management/popup/PlanRequestPopQGrid.jsx index e610a138..955a24ff 100644 --- a/src/components/management/popup/PlanRequestPopQGrid.jsx +++ b/src/components/management/popup/PlanRequestPopQGrid.jsx @@ -1,11 +1,14 @@ import React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { AgGridReact } from 'ag-grid-react' +import { useMessage } from '@/hooks/useMessage' import 'ag-grid-community/styles/ag-grid.css' import 'ag-grid-community/styles/ag-theme-quartz.css' export default function PlanRequestPopQGrid(props) { + const { getMessage } = useMessage() + const { gridData, gridColumns, isPageable = true } = props const [rowData, setRowData] = useState(null) @@ -25,7 +28,7 @@ export default function PlanRequestPopQGrid(props) { } }, []) - const rowBuffer = 100 + const rowBuffer = 20 useEffect(() => { gridData ? setRowData(gridData) : '' @@ -56,6 +59,7 @@ export default function PlanRequestPopQGrid(props) { rowSelection={'singleRow'} pagination={isPageable} onSelectionChanged={onSelectionChanged} + overlayNoRowsTemplate={`${getMessage('stuff.grid.noData')}`} />
    ) diff --git a/src/components/ui/Loading.jsx b/src/components/ui/Loading.jsx deleted file mode 100644 index 59d2170b..00000000 --- a/src/components/ui/Loading.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import style from '@/components/ui/Loading.module.css' - -export default function Loading() { - return -} diff --git a/src/components/ui/Loading.module.css b/src/components/ui/Loading.module.css deleted file mode 100644 index 7b3001f8..00000000 --- a/src/components/ui/Loading.module.css +++ /dev/null @@ -1,35 +0,0 @@ -.loader { - position: relative; - font-size: 48px; - letter-spacing: 6px; -} -.loader:before { - content: 'Loading'; - color: #fff; -} -.loader:after { - content: ''; - width: 20px; - height: 20px; - background-color: #ff3d00; - background-image: radial-gradient(circle 2px, #fff4 100%, transparent 0), radial-gradient(circle 1px, #fff3 100%, transparent 0); - background-position: - 14px -4px, - 12px -1px; - border-radius: 50%; - position: absolute; - margin: auto; - top: -5px; - right: 66px; - transform-origin: center bottom; - animation: fillBaloon 1s ease-in-out infinite alternate; -} - -@keyframes fillBaloon { - 0% { - transform: scale(1); - } - 100% { - transform: scale(3); - } -} diff --git a/src/components/ui/MainSkeleton.jsx b/src/components/ui/MainSkeleton.jsx new file mode 100644 index 00000000..e8b64e17 --- /dev/null +++ b/src/components/ui/MainSkeleton.jsx @@ -0,0 +1,13 @@ +import Skeleton from 'react-loading-skeleton' +import 'react-loading-skeleton/dist/skeleton.css' + +export default function MainSkeleton({ count }) { + return ( + <> +
    + +
    + + + ) +} diff --git a/src/hooks/common/useCanvasConfigInitialize.js b/src/hooks/common/useCanvasConfigInitialize.js index fd5d4542..2b31bbda 100644 --- a/src/hooks/common/useCanvasConfigInitialize.js +++ b/src/hooks/common/useCanvasConfigInitialize.js @@ -1,20 +1,24 @@ import { useEffect } from 'react' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' -import { roofDisplaySelector, settingModalFirstOptionsState } from '@/store/settingAtom' -import { canvasState, dotLineGridSettingState } from '@/store/canvasAtom' -import { setSurfaceShapePattern } from '@/util/canvas-util' +import { basicSettingState, roofDisplaySelector, settingModalFirstOptionsState } from '@/store/settingAtom' +import { canvasState, dotLineGridSettingState, pitchText, pitchTextSelector } from '@/store/canvasAtom' +import { getChonByDegree, getDegreeByChon, setSurfaceShapePattern } from '@/util/canvas-util' import { useFont } from '@/hooks/common/useFont' import { useGrid } from '@/hooks/common/useGrid' import { globalFontAtom } from '@/store/fontAtom' +import { useRoof } from '@/hooks/common/useRoof' export function useCanvasConfigInitialize() { const canvas = useRecoilValue(canvasState) const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState) + const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) const roofDisplay = useRecoilValue(roofDisplaySelector) const setGlobalFonts = useSetRecoilState(globalFontAtom) const setDotLineGridSetting = useSetRecoilState(dotLineGridSettingState) + const pitchText = useRecoilValue(pitchTextSelector) const {} = useFont() const {} = useGrid() + const {} = useRoof() useEffect(() => { if (!canvas) return @@ -27,10 +31,34 @@ export function useCanvasConfigInitialize() { canvas.renderAll() }, [roofDisplay]) + useEffect(() => { + if (!canvas) return + const texts = canvas.getObjects().filter((obj) => obj.name === 'pitchText' || obj.name === 'flowText') + if (basicSetting.roofAngleSet === 'slope') { + texts.forEach((obj) => { + obj.set({ text: `${obj.originText}-∠${obj.pitch}${pitchText}` }) + }) + } + + if (basicSetting.roofAngleSet === 'flat') { + texts.forEach((obj) => { + obj.set({ text: `${obj.originText}-∠${getDegreeByChon(obj.pitch)}${pitchText}` }) + }) + } + + canvas.renderAll() + }, [basicSetting]) + const canvasLoadInit = () => { roofInit() //화면표시 초기화 } + const gridInit = () => { + setDotLineGridSetting((prev) => { + return { ...prev, INTERVAL: { ...prev.INTERVAL } } + }) + } + //치수표시, 화면표시, 글꼴등 초기화 const roofInit = () => { setSettingModalFirstOptions((prev) => { @@ -46,10 +74,7 @@ export function useCanvasConfigInitialize() { }) return { ...prev, option1, option2, dimensionDisplay } }) - - setDotLineGridSetting((prev) => { - return { ...prev } - }) + gridInit() setGlobalFonts((prev) => { const commonText = { ...prev.commonText } @@ -61,5 +86,5 @@ export function useCanvasConfigInitialize() { }) } - return { canvasLoadInit } + return { canvasLoadInit, gridInit } } diff --git a/src/hooks/common/useRoof.js b/src/hooks/common/useRoof.js new file mode 100644 index 00000000..8aee0344 --- /dev/null +++ b/src/hooks/common/useRoof.js @@ -0,0 +1,151 @@ +import { canvasState } from '@/store/canvasAtom' +import { allocDisplaySelector, roofDisplaySelector } from '@/store/settingAtom' +import { useRecoilValue } from 'recoil' +import { useEffect } from 'react' + +export function useRoof() { + const canvas = useRecoilValue(canvasState) + const allocDisplay = useRecoilValue(allocDisplaySelector) + const roofDisplay = useRecoilValue(roofDisplaySelector) + + useEffect(() => { + if (!canvas) return + canvas + .getObjects() + .filter((polygon) => polygon.name === 'roof') + .forEach((polygon) => { + if (allocDisplay) { + setSurfaceShapePattern(polygon, roofDisplay.column) + } else { + polygon.set('fill', null) + } + }) + canvas.renderAll() + }, [allocDisplay]) + + const setSurfaceShapePattern = (polygon, mode = 'onlyBorder') => { + const ratio = window.devicePixelRatio || 1 + + let width = 265 / 10 + let height = 150 / 10 + let roofStyle = 2 + const inputPatternSize = { width: width, height: height } //임시 사이즈 + const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 + + if (polygon.direction === 'east' || polygon.direction === 'west') { + //세로형이면 width height를 바꿈 + ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] + } + + // 패턴 소스를 위한 임시 캔버스 생성 + const patternSourceCanvas = document.createElement('canvas') + patternSourceCanvas.width = polygon.width * ratio + patternSourceCanvas.height = polygon.height * ratio + const ctx = patternSourceCanvas.getContext('2d') + let offset = roofStyle === 1 ? 0 : patternSize.width / 2 + + const rows = Math.floor(patternSourceCanvas.height / patternSize.height) + const cols = Math.floor(patternSourceCanvas.width / patternSize.width) + + ctx.strokeStyle = mode === 'allPainted' ? 'black' : 'green' + ctx.lineWidth = mode === 'allPainted' ? 1 : 0.4 + ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' + + if (polygon.direction === 'east' || polygon.direction === 'west') { + offset = roofStyle === 1 ? 0 : patternSize.height / 2 + for (let col = 0; col <= cols; col++) { + const x = col * patternSize.width + const yStart = 0 + const yEnd = patternSourceCanvas.height + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted') { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + + for (let row = 0; row <= rows; row++) { + const y = row * patternSize.height + (col % 2 === 0 ? 0 : offset) + const xStart = col * patternSize.width + const xEnd = xStart + patternSize.width + ctx.beginPath() + ctx.moveTo(xStart, y) // 선 시작점 + ctx.lineTo(xEnd, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted') { + ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) + } + } + } + } else { + for (let row = 0; row <= rows; row++) { + const y = row * patternSize.height + + ctx.beginPath() + ctx.moveTo(0, y) // 선 시작점 + ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted') { + ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) + } + + for (let col = 0; col <= cols; col++) { + const x = col * patternSize.width + (row % 2 === 0 ? 0 : offset) + const yStart = row * patternSize.height + const yEnd = yStart + patternSize.height + + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted') { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + } + } + } + + const hachingPatternSourceCanvas = document.createElement('canvas') + + if (mode === 'lineHatch') { + hachingPatternSourceCanvas.width = polygon.width * ratio + hachingPatternSourceCanvas.height = polygon.height * ratio + + const ctx1 = hachingPatternSourceCanvas.getContext('2d') + + const gap = 10 + + ctx1.strokeStyle = 'green' // 선 색상 + ctx1.lineWidth = 0.3 // 선 두께 + + for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) { + ctx1.beginPath() + ctx1.moveTo(x, 0) // 선 시작점 + ctx1.lineTo(0, x) // 선 끝점 + ctx1.stroke() + } + } + + const combinedPatternCanvas = document.createElement('canvas') + combinedPatternCanvas.width = polygon.width * ratio + combinedPatternCanvas.height = polygon.height * ratio + const combinedCtx = combinedPatternCanvas.getContext('2d') + + // 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘 + combinedCtx.drawImage(patternSourceCanvas, 0, 0) + combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0) + + // 패턴 생성 + const pattern = new fabric.Pattern({ + source: combinedPatternCanvas, + repeat: 'repeat', + }) + + polygon.set('fill', null) + polygon.set('fill', pattern) + polygon.canvas?.renderAll() + } + + return {} +} diff --git a/src/hooks/option/useFirstOption.js b/src/hooks/option/useFirstOption.js index 5a4605e9..c3778587 100644 --- a/src/hooks/option/useFirstOption.js +++ b/src/hooks/option/useFirstOption.js @@ -33,7 +33,7 @@ export function useFirstOption() { optionName = ['outerLine', 'wallLine'] break case 'gridDisplay': //그리드 표시 - optionName = ['lindGrid', 'dotGrid'] + optionName = ['lineGrid', 'dotGrid', 'adsorptionPoint', 'tempGrid'] break case 'lineDisplay': //지붕선 표시 optionName = ['roof', 'roofBase'] @@ -45,7 +45,7 @@ export function useFirstOption() { optionName = ['7'] break case 'flowDisplay': //흐름방향 표시 - optionName = ['arrow'] + optionName = ['arrow', 'flowText'] break case 'trestleDisplay': //가대 표시 optionName = ['8'] @@ -66,6 +66,8 @@ export function useFirstOption() { //obj.set({ visible: !obj.visible }) }) + canvas.renderAll() + // console.log( // 'optionName', // optionName, diff --git a/src/hooks/roofcover/useEavesGableEdit.js b/src/hooks/roofcover/useEavesGableEdit.js index e062a77a..b0085019 100644 --- a/src/hooks/roofcover/useEavesGableEdit.js +++ b/src/hooks/roofcover/useEavesGableEdit.js @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { useRecoilValue } from 'recoil' -import { canvasState } from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { useMessage } from '@/hooks/useMessage' import { useEvent } from '@/hooks/useEvent' import { LINE_TYPE } from '@/common/common' @@ -9,6 +9,7 @@ import { useMode } from '@/hooks/useMode' import { outerLineFixState } from '@/store/outerLineAtom' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' +import { getChonByDegree } from '@/util/canvas-util' // 처마.케라바 변경 export function useEavesGableEdit(id) { @@ -28,6 +29,8 @@ export function useEavesGableEdit(id) { const { swalFire } = useSwal() const { drawRoofPolygon } = useMode() + const currentAngleType = useRecoilValue(currentAngleTypeSelector) + const pitchText = useRecoilValue(pitchTextSelector) const pitchRef = useRef(null) const offsetRef = useRef(null) @@ -105,13 +108,13 @@ export function useEavesGableEdit(id) { if (radioTypeRef.current === '1') { attributes = { type: LINE_TYPE.WALLLINE.EAVES, - pitch: pitchRef.current.value, + pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value), offset: offsetRef.current.value / 10, } } else { attributes = { type: LINE_TYPE.WALLLINE.HIPANDGABLE, - pitch: pitchRef.current.value, + pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value), offset: offsetRef.current.value / 10, width: widthRef.current.value / 10, } @@ -126,7 +129,7 @@ export function useEavesGableEdit(id) { } else { attributes = { type: LINE_TYPE.WALLLINE.JERKINHEAD, - pitch: pitchRef.current.value, + pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value), offset: offsetRef.current.value / 10, width: widthRef.current.value / 10, } @@ -217,5 +220,5 @@ export function useEavesGableEdit(id) { canvas?.renderAll() } - return { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef } + return { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef, pitchText } } diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index 0f51c900..ef7a4477 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -1,4 +1,4 @@ -import { canvasState, currentObjectState } from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, currentObjectState, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { useEffect, useRef, useState } from 'react' import { useLine } from '@/hooks/useLine' @@ -10,6 +10,7 @@ import { usePolygon } from '@/hooks/usePolygon' import { outerLineFixState } from '@/store/outerLineAtom' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' +import { getChonByDegree } from '@/util/canvas-util' //지붕형상 수동 설정 export function useRoofShapePassivitySetting(id) { @@ -19,6 +20,8 @@ export function useRoofShapePassivitySetting(id) { SHED: 'shed', } const canvas = useRecoilValue(canvasState) + const currentAngleType = useRecoilValue(currentAngleTypeSelector) + const pitchText = useRecoilValue(pitchTextSelector) const { getMessage } = useMessage() const { showLine, hideLine, addPitchTextsByOuterLines } = useLine() const { swalFire } = useSwal() @@ -34,6 +37,7 @@ export function useRoofShapePassivitySetting(id) { const isFix = useRef(false) const initLines = useRef([]) const [isLoading, setIsLoading] = useState(false) + const { closePopup } = usePopup() const buttons = [ { id: 1, name: getMessage('eaves'), type: TYPES.EAVES }, @@ -134,12 +138,12 @@ export function useRoofShapePassivitySetting(id) { attributes = { type: LINE_TYPE.WALLLINE.EAVES, offset, - pitch: pitchRef.current.value, + pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value), } } else if (type === TYPES.GABLE) { attributes = { type: LINE_TYPE.WALLLINE.GABLE, - pitch: pitchRef.current.value, + pitch: currentAngleType === ANGLE_TYPE.SLOPE ? pitchRef.current.value : getChonByDegree(pitchRef.current.value), offset, } } else if (type === TYPES.SHED) { @@ -216,5 +220,5 @@ export function useRoofShapePassivitySetting(id) { canvas.renderAll() } - return { handleSave, handleConfirm, buttons, type, setType, TYPES, offsetRef, pitchRef, handleRollback } + return { handleSave, handleConfirm, buttons, type, setType, TYPES, offsetRef, pitchRef, handleRollback, pitchText } } diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index 13dfef8b..9aab5da7 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import { useMessage } from '@/hooks/useMessage' import { useRecoilValue, useSetRecoilState } from 'recoil' -import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState, pitchTextSelector } from '@/store/canvasAtom' import { LINE_TYPE } from '@/common/common' import { usePolygon } from '@/hooks/usePolygon' import { useMode } from '@/hooks/useMode' @@ -9,6 +9,7 @@ import { useLine } from '@/hooks/useLine' import { outerLineFixState } from '@/store/outerLineAtom' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' +import { getChonByDegree } from '@/util/canvas-util' // 지붕형상 설정 export function useRoofShapeSetting(id) { @@ -17,13 +18,16 @@ export function useRoofShapeSetting(id) { const { swalFire } = useSwal() const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) + const currentAngleType = useRecoilValue(currentAngleTypeSelector) + const pitchText = useRecoilValue(pitchTextSelector) const { addPolygonByLines } = usePolygon() - const [pitch, setPitch] = useState(4) + + const [pitch, setPitch] = useState(currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8) // 경사 const [eavesOffset, setEavesOffset] = useState(500) // 처마출폭 const [gableOffset, setGableOffset] = useState(300) // 케라바출폭 const [sleeveOffset, setSleeveOffset] = useState(300) // 소매출폭 const [jerkinHeadWidth, setJerkinHeadWidth] = useState(800) // 반절처 폭 - const [jerkinHeadPitch, setJerkinHeadPitch] = useState(4.5) // 반절처 경사 + const [jerkinHeadPitch, setJerkinHeadPitch] = useState(currentAngleType === ANGLE_TYPE.SLOPE ? 4.5 : 20) // 반절처 경사 const [hipAndGableWidth, setHipAndGableWidth] = useState(800) // 팔작지붕 폭 const [shedWidth, setShedWidth] = useState(300) // 한쪽흐름 폭 const [hasSleeve, setHasSleeve] = useState('0') @@ -34,9 +38,19 @@ export function useRoofShapeSetting(id) { const setCurrentMenu = useSetRecoilState(currentMenuState) const outerLineFix = useRecoilValue(outerLineFixState) + const pitchRef = useRef(null) + const jerkinHeadPitchRef = useRef(null) + const history = useRef([]) const { closePopup } = usePopup() + useEffect(() => { + pitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? pitch : getChonByDegree(pitch) + }, [pitch]) + useEffect(() => { + jerkinHeadPitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? jerkinHeadPitch : getChonByDegree(jerkinHeadPitch) + }, [jerkinHeadPitch]) + useEffect(() => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') if (!outerLineFix || outerLines.length === 0) { @@ -114,12 +128,10 @@ export function useRoofShapeSetting(id) { canvas?.renderAll() } - setPitch(4) setEavesOffset(500) setGableOffset(300) setSleeveOffset(300) setJerkinHeadWidth(800) - setJerkinHeadPitch(4.5) setHipAndGableWidth(800) setShedWidth(300) }, [shapeNum]) @@ -181,7 +193,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'bottom') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -197,7 +209,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'top') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -205,7 +217,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'bottom') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -226,7 +238,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'top') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -234,7 +246,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'bottom') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -242,7 +254,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'bottom') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -250,7 +262,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'top') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -270,7 +282,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'right') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -278,7 +290,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'left') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -286,7 +298,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'left') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -294,7 +306,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'right') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -315,7 +327,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'left') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -323,7 +335,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'right') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -331,7 +343,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'right') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -339,7 +351,7 @@ export function useRoofShapeSetting(id) { if (line.direction === 'left') { line.attributes = { offset: shedWidth / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.SHED, } } @@ -436,7 +448,7 @@ export function useRoofShapeSetting(id) { outerLines.forEach((line) => { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } // hideLine(line) @@ -458,7 +470,7 @@ export function useRoofShapeSetting(id) { } else if (line.direction === 'top' || line.direction === 'bottom') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -481,7 +493,7 @@ export function useRoofShapeSetting(id) { } else if (line.direction === 'left' || line.direction === 'right') { line.attributes = { offset: eavesOffset / 10, - pitch: pitch, + pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, } } @@ -504,7 +516,7 @@ export function useRoofShapeSetting(id) { // 처마 attributes = { type: LINE_TYPE.WALLLINE.EAVES, - pitch: pitch, + pitch: pitchRef.current, offset: eavesOffset / 10, } addPitchText(currentObject) @@ -535,7 +547,7 @@ export function useRoofShapeSetting(id) { // 팔작지붕 attributes = { type: LINE_TYPE.WALLLINE.HIPANDGABLE, - pitch: pitch, + pitch: pitchRef.current, offset: eavesOffset / 10, width: hipAndGableWidth / 10, } @@ -550,7 +562,7 @@ export function useRoofShapeSetting(id) { type: LINE_TYPE.WALLLINE.JERKINHEAD, offset: gableOffset / 10, width: jerkinHeadWidth / 10, - pitch: jerkinHeadPitch, + pitch: jerkinHeadPitchRef.current, } addPitchText(currentObject) selectedLine.set({ strokeWidth: 4 }) @@ -629,5 +641,6 @@ export function useRoofShapeSetting(id) { setButtonAct, handleConfirm, handleRollBack, + pitchText, } } diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index 17f00eea..886407b4 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -12,7 +12,6 @@ import { defineQPloygon } from '@/util/qpolygon-utils' import { writeImage } from '@/lib/canvas' import { useCanvasEvent } from '@/hooks/useCanvasEvent' import { useAxios } from '@/hooks/useAxios' -import { v4 as uuidv4 } from 'uuid' import { useFont } from '@/hooks/common/useFont' export function useCanvas(id) { @@ -93,8 +92,6 @@ export function useCanvas(id) { canvas.getObjects().length > 0 && canvas?.clear() // settings for all canvas in the app fabric.Object.prototype.transparentCorners = false - fabric.Object.prototype.id = uuidv4() - fabric.Object.prototype.uuid = uuidv4() fabric.Object.prototype.selectable = true fabric.Object.prototype.lockMovementX = true fabric.Object.prototype.lockMovementY = true diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index bf878c7b..0df650c0 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -1,7 +1,15 @@ import { useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' import { v4 as uuidv4 } from 'uuid' -import { canvasSizeState, canvasState, canvasZoomState, currentObjectState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' +import { + canvasSizeState, + canvasState, + canvasZoomState, + currentObjectState, + fontFamilyState, + fontSizeState, + modifiedPlanFlagState, +} from '@/store/canvasAtom' import { QPolygon } from '@/components/fabric/QPolygon' // 캔버스에 필요한 이벤트 @@ -13,6 +21,7 @@ export function useCanvasEvent() { const fontSize = useRecoilValue(fontSizeState) const fontFamily = useRecoilValue(fontFamilyState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) + const [modifiedPlanFlag, setModifiedPlanFlag] = useRecoilState(modifiedPlanFlagState) // 기본적인 이벤트 필요시 추가 const attachDefaultEventOnCanvas = () => { @@ -34,14 +43,32 @@ export function useCanvasEvent() { onChange: (e) => { const target = e.target + if (target.name !== 'mouseLine') { + if (!modifiedPlanFlag) { + setModifiedPlanFlag((prev) => !prev) + } + } + if (target) { - target.uuid = uuidv4() // settleDown(target) } }, addEvent: (e) => { const target = e.target + if (!target.id) { + target.id = uuidv4() + } + if (!target.uuid) { + target.uuid = uuidv4() + } + + if (target.name !== 'mouseLine') { + if (!modifiedPlanFlag) { + setModifiedPlanFlag((prev) => !prev) + } + } + if (target.type === 'QPolygon' || target.type === 'QLine') { const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') textObjs.forEach((obj) => { @@ -141,6 +168,9 @@ export function useCanvasEvent() { })*/ target.on('moving', (e) => { + target.uuid = uuidv4() + setModifiedPlanFlag((prev) => !prev) + if (target.parentDirection === 'left' || target.parentDirection === 'right') { const minX = target.minX const maxX = target.maxX diff --git a/src/hooks/useContextMenu.js b/src/hooks/useContextMenu.js index adb9b680..7549f8e1 100644 --- a/src/hooks/useContextMenu.js +++ b/src/hooks/useContextMenu.js @@ -20,20 +20,25 @@ import DimensionLineSetting from '@/components/floor-plan/modal/dimensionLine/Di import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting' import LinePropertySetting from '@/components/floor-plan/modal/lineProperty/LinePropertySetting' import FlowDirectionSetting from '@/components/floor-plan/modal/flowDirection/FlowDirectionSetting' -import { useCommonUtils } from './common/useCommonUtils' -import { canvasState } from '@/store/canvasAtom' -export function useContextMenu({ externalFn }) { +import { useCommonUtils } from './common/useCommonUtils' +import { useMessage } from '@/hooks/useMessage' +import { useCanvasEvent } from '@/hooks/useCanvasEvent' +import { contextMenuState } from '@/store/contextMenu' + +export function useContextMenu() { const currentMenu = useRecoilValue(currentMenuState) // 현재 메뉴 const setContextPopupPosition = useSetRecoilState(contextPopupPositionState) // 현재 메뉴 const [contextMenu, setContextMenu] = useState([[]]) // 메뉴.object 별 context menu const [currentContextMenu, setCurrentContextMenu] = useState(null) // 선택한 contextMenu const currentObject = useRecoilValue(currentObjectState) + const { getMessage } = useMessage() const { addPopup } = usePopup() const [popupId, setPopupId] = useState(uuidv4()) const [gridColor, setGridColor] = useRecoilState(gridColorState) - const canvas = useRecoilValue(canvasState) const { deleteObject, moveObject, copyObject, editText, changeDimensionExtendLine } = useCommonUtils({}) + const [qContextMenu, setQContextMenu] = useRecoilState(contextMenuState) + const { handleZoomClear } = useCanvasEvent() const currentMenuSetting = (position) => { switch (currentMenu) { @@ -42,26 +47,26 @@ export function useContextMenu({ externalFn }) { [ { id: 'gridMove', - name: '그리드 이동', + name: getMessage('modal.grid.move'), component: , }, { id: 'gridCopy', - name: '그리드 복사', + name: getMessage('modal.grid.copy'), component: , }, { id: 'gridColorEdit', - name: '그리드 색 변경', + name: getMessage('modal.grid.color.edit'), component: , }, { id: 'remove', - name: '삭제', + name: getMessage('delete'), }, { id: 'removeAll', - name: '전체 삭제', + name: getMessage('delete.all'), }, ], ]) @@ -80,63 +85,64 @@ export function useContextMenu({ externalFn }) { [ { id: 'refresh', - name: '새로고침', - fn: () => { - externalFn.handleZoomClear() - }, + name: getMessage('refresh'), + fn: () => handleZoomClear(), }, { id: 'roofMaterialPlacement', - name: '지붕재 배치', + name: getMessage('contextmenu.roof.material.placement'), }, { id: 'roofMaterialRemove', - name: '지붕재 삭제', + name: getMessage('contextmenu.roof.material.remove'), }, { id: 'roofMaterialRemoveAll', - name: '지붕재 전체 삭제', + name: getMessage('contextmenu.roof.material.remove.all'), }, { id: 'selectMove', - name: '선택・이동', + name: getMessage('contextmenu.select.move'), }, { id: 'wallLineRemove', - name: '외벽선 삭제', + name: getMessage('contextmenu.wallline.remove'), }, ], [ { id: 'sizeEdit', - name: '사이즈 변경', + name: getMessage('contextmenu.size.edit'), component: , }, { id: 'auxiliaryMove', - name: '보조선 이동(M)', + name: `${getMessage('contextmenu.auxiliary.move')}(M)`, + shortcut: ['m', 'M'], component: , }, { id: 'auxiliaryCopy', - name: '보조선 복사(C)', + name: `${getMessage('contextmenu.auxiliary.copy')}(C)`, + shortcut: ['c', 'C'], component: , }, { id: 'auxiliaryRemove', - name: '보조선 삭제(D)', + shortcut: ['d', 'D'], + name: `${getMessage('contextmenu.auxiliary.remove')}(D)`, }, { id: 'auxiliaryVerticalBisector', - name: '보조선 수직이등분선', + name: getMessage('contextmenu.auxiliary.vertical.bisector'), }, { id: 'auxiliaryCut', - name: '보조선 절삭', + name: getMessage('contextmenu.auxiliary.cut'), }, { id: 'auxiliaryRemoveAll', - name: '보조선 전체 삭제', + name: getMessage('contextmenu.auxiliary.remove.all'), }, ], ]) @@ -151,33 +157,37 @@ export function useContextMenu({ externalFn }) { [ { id: 'sizeEdit', - name: '사이즈 변경', + name: getMessage('contextmenu.size.edit'), }, { id: 'remove', - name: '삭제(D)', + shortcut: ['d', 'D'], + name: `${getMessage('contextmenu.remove')}(D)`, }, { id: 'move', - name: '이동(M)', + shortcut: ['m', 'M'], + name: `${getMessage('contextmenu.move')}(M)`, }, { id: 'copy', - name: '복사(C)', + shortcut: ['c', 'C'], + name: `${getMessage('contextmenu.copy')}(C)`, }, ], [ { id: 'roofMaterialEdit', - name: '지붕재 변경', + name: getMessage('contextmenu.roof.material.edit'), }, { id: 'linePropertyEdit', - name: '각 변 속성 변경', + name: getMessage('contextmenu.line.property.edit'), + component: , }, { id: 'flowDirectionEdit', - name: '흐름 방향 변경', + name: getMessage('contextmenu.flow.direction.edit'), }, ], ]) @@ -189,11 +199,29 @@ export function useContextMenu({ externalFn }) { } const handleClick = (e, menu) => { + if (menu?.fn) { + menu.fn() + } setContextPopupPosition({ - x: e.clientX, - y: e.clientY, + x: e?.clientX, + y: e?.clientY, }) setCurrentContextMenu(menu) + setQContextMenu({ ...qContextMenu, visible: false }) + } + + const handleKeyup = (e) => { + let menu = null + + for (let i = 0; i < contextMenu.length; i++) { + const temp = contextMenu[i].filter((menu) => { + return menu.shortcut?.includes(e.key) + }) + + if (temp.length > 0) menu = temp + } + + handleClick(null, menu) } useEffect(() => { @@ -205,7 +233,6 @@ export function useContextMenu({ externalFn }) { }, [currentContextMenu]) useEffect(() => { - console.log('object name', currentObject) if (currentObject?.name) { switch (currentObject.name) { case 'triangleDormer': @@ -214,29 +241,32 @@ export function useContextMenu({ externalFn }) { [ { id: 'sizeEdit', - name: '사이즈 변경', - component: , + name: getMessage('contextmenu.size.edit'), + component: , }, { id: 'dormerRemove', - name: '삭제(D)', + shortcut: ['d', 'D'], + name: `${getMessage('contextmenu.remove')}(D)`, }, { id: 'dormerMove', - name: '이동(M)', + shortcut: ['m', 'M'], + name: `${getMessage('contextmenu.move')}(M)`, }, { id: 'dormerCopy', - name: '복사(C)', + shortcut: ['c', 'C'], + name: `${getMessage('contextmenu.copy')}(C)`, }, { id: 'roofMaterialEdit', - name: '지붕재 변경', + name: getMessage('contextmenu.roof.material.edit'), component: , }, { id: 'dormerOffset', - name: '도머 오프셋', + name: getMessage('contextmenu.dormer.offset'), component: , }, ], @@ -252,32 +282,35 @@ export function useContextMenu({ externalFn }) { }, { id: 'roofMaterialRemove', - name: '삭제(D)', + shortcut: ['d', 'D'], + name: `${getMessage('contextmenu.remove')}(D)`, }, { id: 'roofMaterialMove', - name: '이동(M)', + shortcut: ['m', 'M'], + name: `${getMessage('contextmenu.move')}(M)`, }, { id: 'roofMaterialCopy', - name: '복사(C)', + shortcut: ['c', 'C'], + name: `${getMessage('contextmenu.copy')}(C)`, }, ], [ { id: 'roofMaterialEdit', - name: '지붕재 변경', + name: getMessage('contextmenu.roof.material.edit'), component: , }, { id: 'linePropertyEdit', - name: '각 변 속성 변경', + name: getMessage('contextmenu.line.property.edit'), component: , }, { id: 'flowDirectionEdit', - name: '흐름 뱡향 변경', - component: , + name: getMessage('contextmenu.flow.direction.edit'), + component: , }, ], ]) @@ -287,23 +320,26 @@ export function useContextMenu({ externalFn }) { [ { id: 'sizeEdit', - name: '사이즈 변경', + name: getMessage('contextmenu.size.edit'), }, { id: 'openingRemove', - name: '삭제(D)', + shortcut: ['d', 'D'], + name: `${getMessage('contextmenu.remove')}(D)`, }, { id: 'openingMove', - name: '이동(M)', + shortcut: ['m', 'M'], + name: `${getMessage('contextmenu.move')}(M)`, }, { id: 'openingCopy', - name: '복사(C)', + shortcut: ['c', 'C'], + name: `${getMessage('contextmenu.copy')}(C)`, }, { id: 'openingOffset', - name: '개구 오프셋', + name: getMessage('contextmenu.opening.offset'), }, ], ]) @@ -313,19 +349,19 @@ export function useContextMenu({ externalFn }) { [ { id: 'lengthTextRemove', - name: '삭제', + name: getMessage('contextmenu.remove'), }, { id: 'lengthTextMove', - name: '이동', + name: getMessage('contextmenu.move'), }, { id: 'lengthTextAuxiliaryLineEdit', - name: '치수 보조선 변경', + name: getMessage('contextmenu.dimension.auxiliary.line.edit'), }, { id: 'displayEdit', - name: '표시 변경', + name: getMessage('contextmenu.display.edit'), }, ], ]) @@ -335,27 +371,27 @@ export function useContextMenu({ externalFn }) { [ { id: 'commonTextRemove', - name: '삭제', + name: getMessage('contextmenu.remove'), fn: () => deleteObject(), }, { id: 'commonTextMove', - name: '이동', + name: getMessage('contextmenu.move'), fn: () => moveObject(), }, { id: 'commonTextCopy', - name: '복사', + name: getMessage('contextmenu.copy'), fn: () => copyObject(), }, { id: 'commonTextFontSetting', - name: '폰트 설정', + name: getMessage('contextmenu.font.setting'), component: , }, { id: 'commonTextEdit', - name: '편집', + name: getMessage('contextmenu.edit'), fn: () => editText(), }, ], @@ -366,23 +402,23 @@ export function useContextMenu({ externalFn }) { [ { id: 'gridMove', - name: '그리드 이동', + name: getMessage('modal.grid.move'), }, { id: 'gridCopy', - name: '그리드 복사', + name: getMessage('modal.grid.copy'), }, { id: 'gridColorEdit', - name: '그리드 색 변경', + name: getMessage('contextmenu.grid.color.edit'), }, { id: 'remove', - name: '삭제', + name: getMessage('contextmenu.remove'), }, { id: 'removeAll', - name: '전체 삭제', + name: getMessage('contextmenu.remove.all'), }, ], ]) @@ -392,22 +428,22 @@ export function useContextMenu({ externalFn }) { [ { id: 'dimensionLineRemove', - name: '삭제', + name: getMessage('contextmenu.remove'), fn: () => deleteObject(), }, { id: 'dimensionLineMove', - name: '이동', + name: getMessage('contextmenu.move'), fn: () => moveObject(), }, { id: 'dimensionAuxiliaryLineEdit', - name: '치수 보조선 변경', + name: getMessage('contextmenu.dimension.auxiliary.line.edit'), fn: () => changeDimensionExtendLine(), }, { id: 'dimensionLineDisplayEdit', - name: '표시 변경', + name: getMessage('contextmenu.display.edit'), component: , }, ], @@ -418,20 +454,23 @@ export function useContextMenu({ externalFn }) { [ { id: 'sizeEdit', - name: '사이즈 변경', + name: getMessage('contextmenu.size.edit'), component: , }, { id: 'remove', - name: '삭제(D)', + shortcut: ['d', 'D'], + name: `${getMessage('contextmenu.remove')}(D)`, }, { id: 'move', - name: '이동(M)', + shortcut: ['m', 'M'], + name: `${getMessage('contextmenu.move')}(M)`, }, { id: 'copy', - name: '복사(C)', + shortcut: ['c', 'C'], + name: `${getMessage('contextmenu.copy')}(C)`, }, ], ]) @@ -449,5 +488,6 @@ export function useContextMenu({ externalFn }) { currentContextMenu, setCurrentContextMenu, handleClick, + handleKeyup, } } diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index 210c1ac6..62ec937a 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -1,11 +1,13 @@ import { useRecoilValue } from 'recoil' -import { canvasState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' +import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' export const useLine = () => { const canvas = useRecoilValue(canvasState) const fontSize = useRecoilValue(fontSizeState) const fontFamily = useRecoilValue(fontFamilyState) + const currentAngleType = useRecoilValue(currentAngleTypeSelector) const addLine = (points = [], options) => { const line = new QLine(points, { @@ -77,6 +79,11 @@ export const useLine = () => { let left, top + const textStr = + currentAngleType === ANGLE_TYPE.SLOPE + ? `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${attributes.pitch ? '-∠' + attributes.pitch : ''}` + : `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${attributes.pitch ? '-∠' + getDegreeByChon(attributes.pitch) : ''}` + if (direction === 'top') { left = (startPoint.x + endPoint.x) / 2 top = (startPoint.y + endPoint.y) / 2 - 50 @@ -91,17 +98,20 @@ export const useLine = () => { top = (startPoint.y + endPoint.y) / 2 - 30 } - const text = new fabric.Text( - `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${attributes.pitch ? '-∠' + attributes.pitch : ''}`, - { - left, - top, - fontSize: 20, - fill: 'black', - name: 'pitchText', - parentId: line.id, - }, - ) + if (!attributes.pitch) { + return + } + + const text = new fabric.Text(`${textStr}`, { + left, + top, + fontSize: 20, + originText: `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}`, + fill: 'black', + name: 'pitchText', + parentId: line.id, + pitch: attributes.pitch, + }) canvas.add(text) } diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index 19b9a879..8ed84af4 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { useRecoilState } from 'recoil' -import { v4 as uuidv4 } from 'uuid' -import { canvasState, currentCanvasPlanState, initCanvasPlansState, plansState, modifiedPlansState } from '@/store/canvasAtom' +import { v4 as uuidv4, validate as isValidUUID } from 'uuid' +import { canvasState, currentCanvasPlanState, initCanvasPlansState, plansState, modifiedPlansState, modifiedPlanFlagState } from '@/store/canvasAtom' import { useAxios } from '@/hooks/useAxios' import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' @@ -14,6 +14,7 @@ export function usePlan() { const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState) // DB에 저장된 plan const [plans, setPlans] = useRecoilState(plansState) // 전체 plan (DB에 저장된 plan + 저장 안된 새로운 plan) const [modifiedPlans, setModifiedPlans] = useRecoilState(modifiedPlansState) // 변경된 canvas plan + const [modifiedPlanFlag, setModifiedPlanFlag] = useRecoilState(modifiedPlanFlagState) // 캔버스 실시간 오브젝트 이벤트 감지 flag const { swalFire } = useSwal() const { getMessage } = useMessage() @@ -39,6 +40,7 @@ export function usePlan() { 'length', 'idx', 'direction', + 'parentDirection', 'lines', 'points', 'lockMovementX', @@ -89,30 +91,29 @@ export function usePlan() { } /** - * 캔버스에서 발생하는 실시간 오브젝트 이벤트를 감지하여 수정 여부를 판단 + * 캔버스에서 발생하는 실시간 오브젝트 이벤트를 감지하여 수정 여부를 확인 후 관리 */ - const checkCanvasObjectEvent = (e, planId) => { + const checkCanvasObjectEvent = (planId) => { if (!modifiedPlans.some((modifiedPlan) => modifiedPlan === planId) && checkModifiedCanvasPlan(planId)) { - setModifiedPlans([...modifiedPlans, planId]) + setModifiedPlans((prev) => [...prev, planId]) + setModifiedPlanFlag(false) } } - /** * 현재 캔버스 상태와 DB에 저장된 캔버스 상태를 비교하여 수정 여부를 판단 */ const checkModifiedCanvasPlan = (planId) => { const canvasStatus = currentCanvasData() - const initPlanData = initCanvasPlans.find((plan) => plan.id === planId) - - if (!initPlanData) { + if (isValidUUID(planId)) { // 새로운 캔버스 return JSON.parse(canvasStatus).objects.length > 0 } else { // 저장된 캔버스 // 각각 object들의 uuid 목록을 추출하여 비교 const canvasObjsUuids = getObjectUuids(JSON.parse(canvasStatus).objects) + const initPlanData = initCanvasPlans.find((plan) => plan.id === planId) const dbObjsUuids = getObjectUuids(JSON.parse(initPlanData.canvasStatus).objects) - return canvasObjsUuids.length !== dbObjsUuids.length || !canvasObjsUuids.every((id, index) => id === dbObjsUuids[index]) + return canvasObjsUuids.length !== dbObjsUuids.length || !canvasObjsUuids.every((uuid, index) => uuid === dbObjsUuids[index]) } } const getObjectUuids = (objects) => { @@ -121,10 +122,12 @@ export function usePlan() { .map((obj) => obj.uuid) .sort() } - /** - * 캔버스에 저장되지 않은 변경사항이 있는 경우 저장 여부를 확인 후 저장 - */ - const checkUnsavedCanvasPlan = async () => { + + const resetModifiedPlans = () => { + setModifiedPlans([]) + setModifiedPlanFlag(false) + } + const checkUnsavedCanvasPlan = (str) => { if (modifiedPlans.length > 0) { swalFire({ text: `${currentCanvasPlan.name} ` + getMessage('plan.message.confirm.save'), @@ -364,7 +367,7 @@ export function usePlan() { plans, modifiedPlans, checkCanvasObjectEvent, - checkUnsavedCanvasPlan, + resetModifiedPlans, saveCanvas, handleCurrentPlan, handleAddPlan, diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 71ec1549..c147e2fe 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,7 @@ -import { canvasState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { getDirectionByPoint } from '@/util/canvas-util' +import { getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' @@ -12,6 +12,8 @@ export const usePolygon = () => { const isFlowDisplay = useRecoilValue(flowDisplaySelector) const flowFontOptions = useRecoilValue(fontSelector('flowText')) const lengthTextFontOptions = useRecoilValue(fontSelector('lengthText')) + const currentAngleType = useRecoilValue(currentAngleTypeSelector) + const pitchText = useRecoilValue(pitchTextSelector) const addPolygon = (points, options) => { const polygon = new QPolygon(points, { @@ -401,14 +403,18 @@ export const usePolygon = () => { const addTextByArrows = (arrows, txt, canvas) => { arrows.forEach((arrow, index) => { - const text = new fabric.Text(`${txt}${index + 1} (${arrow.pitch}寸)`, { + const textStr = `${txt}${index + 1} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})` + + const text = new fabric.Text(`${textStr}`, { fontSize: flowFontOptions.fontSize.value, fill: flowFontOptions.fontColor.value, fontFamily: flowFontOptions.fontFamily.value, fontWeight: flowFontOptions.fontWeight.value, + pitch: arrow.pitch, originX: 'center', originY: 'center', name: 'flowText', + originText: `${txt}${index + 1}`, selectable: false, left: arrow.stickeyPoint.x, top: arrow.stickeyPoint.y, diff --git a/src/locales/ja.json b/src/locales/ja.json index c5522fe2..97e20595 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -192,6 +192,8 @@ "modal.grid.copy.info": "間隔を設定し、コピー方向を選択します", "modal.grid.copy.length": "長さ", "modal.grid.copy.save": "保存", + "modal.grid.color.edit": "그리드 색 변경(JA)", + "modal.dormer.offset.info": "移動する方向を入力してください", "modal.common.save": "保存", "modal.common.add": "追加", "modal.common.prev": "以前", @@ -261,12 +263,54 @@ "modal.placement.surface.setting.diagonal.length": "斜めの長さ", "modal.color.picker.title": "色の設定", "modal.color.picker.default.color": "基本色", + "modal.size.setting": "サイズ変更", + "modal.shape.flow.direction.setting": "面フローの設定", + "modal.shape.flow.direction.setting.orientation.setting.info": "シミュレーション計算の方向を指定します。面が向いている方位を選択してください。", + "modal.shape.flow.direction.setting.orientation.8": "8方位に選ぶ", + "modal.shape.flow.direction.setting.orientation.24": "24方位から選択する (表記は8方位です。)", + "modal.panel.batch.statistic": "パネル配置集計", + "modal.panel.batch.statistic.roof.shape": "屋根面", + "modal.panel.batch.statistic.power.generation.amount": "発電量", + "modal.panel.batch.statistic.total": "合計", + "modal.flow.direction.setting": "流れ方向の設定", + "modal.flow.direction.setting.info": "流れ方向を選択してください。", "plan.message.confirm.save": "PLAN을 저장하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.save": "저장되었습니다.", "plan.message.delete": "삭제되었습니다.", "setting": "設定", + "delete": "삭제(JA)", + "delete.all": "전체 삭제(JA)", + "refresh": "새로고침(JA)", + "contextmenu.roof.material.placement": "지붕재 배치(JA)", + "contextmenu.roof.material.edit": "지붕재 변경(JA)", + "contextmenu.roof.material.remove": "지붕재 삭제(JA)", + "contextmenu.roof.material.remove.all": "지붕재 전체 삭제(JA)", + "contextmenu.dormer.offset": "도머 오프셋(JA)", + "contextmenu.select.move": "선택・이동(JA)", + "contextmenu.wallline.remove": "외벽선 삭제(JA)", + "contextmenu.size.edit": "サイズ変更", + "contextmenu.auxiliary.move": "보조선 이동(JA)", + "contextmenu.auxiliary.copy": "보조선 복사(JA)", + "contextmenu.auxiliary.remove": "보조선 삭제(JA)", + "contextmenu.auxiliary.vertical.bisector": "보조선 수직이등분선(JA)", + "contextmenu.auxiliary.cut": "보조선 절삭(JA)", + "contextmenu.auxiliary.remove.all": "보조선 전체 삭제(JA)", + "contextmenu.line.property.edit": "各辺属性の変更", + "modal.line.property.edit.info": "属性を変更する辺を選択してください。", + "modal.line.property.edit.selected": "選択した値", + "contextmenu.flow.direction.edit": "흐름 방향 변경(JA)", + "contextmenu.font.setting": "폰트 설정(JA)", + "contextmenu.grid.color.edit": "그리드 색 변경(JA)", + "contextmenu.dimension.auxiliary.line.edit": "치수 보조선 변경(JA)", + "contextmenu.display.edit": "표시 변경(JA)", + "contextmenu.opening.offset": "개구 오프셋(JA)", + "contextmenu.remove": "삭제(JA)", + "contextmenu.remove.all": "전체 삭제(JA)", + "contextmenu.move": "이동(JA)", + "contextmenu.copy": "복사(JA)", + "contextmenu.edit": "편집(JA)", "common.message.no.data": "No data", "common.message.no.dataDown": "ダウンロードするデータがありません", "common.message.noData": "表示するデータがありません", @@ -357,7 +401,7 @@ "common.require": "必須", "commons.west": "立つ", "commons.east": "ドン", - "commons.south": "立つ", + "commons.south": "南", "commons.north": "北", "site.name": "Q.CAST III", "site.sub_name": "太陽光発電システム図面管理サイト", @@ -376,6 +420,7 @@ "board.sub.total": "全体", "board.sub.fileList": "添付ファイル一覧", "board.sub.updDt": "更新日", + "board.sub.btn.close": "閉じる", "myinfo.title": "マイプロフィール", "myinfo.info.userId": "ユーザーID", "myinfo.info.nameKana": "担当者名ふりがな", @@ -445,6 +490,8 @@ "join.complete.title": "Q.CAST3 ログインID発行申請完了", "join.complete.contents": "※ 申請したIDが承認されると、担当者情報に入力されたメールアドレスにログイン案内メールが送信されます。", "join.complete.email_comment": "担当者メールアドレス", + "join.validation.check1": "{0} の形式を確認してください。", + "join.complete.save.confirm": "Hanwha Japan担当者にID承認を要請されると、これ以上情報を修正できません。 本当にリクエストしますか?", "stuff.gridHeader.lastEditDatetime": "更新日時", "stuff.gridHeader.objectNo": "品番", "stuff.gridHeader.planTotCnt": "プラン数", @@ -486,6 +533,7 @@ "stuff.detail.saleStoreId": "一次販売店名/ID", "stuff.detail.otherSaleStoreId": "二次販売店名/ID", "stuff.detail.zipNo": "郵便番号 ", + "stuff.detail.address": "住所 ", "stuff.detail.btn.addressPop": "住所検索", "stuff.detail.btn.addressPop.guide": "※ 郵便番号7桁を入力した後、アドレス検索ボタンをクリックしてください", "stuff.detail.prefId": "都道府県 / 住所 ", @@ -584,6 +632,7 @@ "stuff.detail.planGridHeader.management": "管理", "stuff.detail.planGrid.btn1": "見積書の照会", "stuff.detail.planGrid.btn2": "Excel", + "stuff.grid.noData": "照会されたデータがありません", "length": "長さ", "height": "高さ", "output": "出力", @@ -595,9 +644,23 @@ "size": "寸", "size.angle": "寸(度)", "eaves": "軒", + "eaves.line": "軒先", "gable": "ケラバ", + "gable.left": "ケラバ左", + "gable.right": "ケラバ右", + "ridge": "龍丸", + "oneside.flow.ridge": "片側の流れ", + "yosemune": "ヨセムネ", + "valley": "谷", + "l.abandon.valley": "Lの捨て渓谷", + "mansard": "マンサード", "wall": "壁", "wall.merge": "壁取り", + "wall.merge.type": "壁取り(型)", + "wall.merge.flow": "壁取合(流れ)", + "wall.merge.flow.left": "壁取合(流れ左)", + "wall.merge.flow.right": "壁取り(流れ右)", + "no.setting": "설정없음", "hajebichi": "ハゼビーチ", "straight.line": "直線", "right.angle": "直角", diff --git a/src/locales/ko.json b/src/locales/ko.json index 529dc494..cc47a03d 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -196,6 +196,8 @@ "modal.grid.copy.info": "간격을 설정하고 복사 방향을 선택하십시오", "modal.grid.copy.length": "길이", "modal.grid.copy.save": "저장", + "modal.grid.color.edit": "그리드 색 변경", + "modal.dormer.offset.info": "이동할 거리와 방향을 입력해주세요.", "modal.common.save": "저장", "modal.common.add": "추가", "modal.common.prev": "이전", @@ -266,12 +268,54 @@ "modal.placement.surface.setting.diagonal.length": "대각선 길이", "modal.color.picker.title": "색 설정", "modal.color.picker.default.color": "기본색상", + "modal.size.setting": "사이즈 변경", + "modal.shape.flow.direction.setting": "면 흐름 설정", + "modal.shape.flow.direction.setting.orientation.setting.info": "시뮬레이션 계산용 방위를 지정합니다. 면이 향하고 있는 방위를 선택해 주세요.", + "modal.shape.flow.direction.setting.orientation.8": "8방위로 선택한다.", + "modal.shape.flow.direction.setting.orientation.24": "24방위로 선택한다.(표기는 8방위입니다.)", + "modal.panel.batch.statistic": "패널 배치 집계", + "modal.panel.batch.statistic.roof.shape": "지붕면", + "modal.panel.batch.statistic.power.generation.amount": "발전량", + "modal.panel.batch.statistic.total": "합계", + "modal.flow.direction.setting": "흐름 방향 설정", + "modal.flow.direction.setting.info": "흐름방향을 선택하세요.", "plan.message.confirm.save": "PLAN을 저장하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.save": "저장되었습니다.", "plan.message.delete": "삭제되었습니다.", "setting": "설정", + "delete": "삭제", + "delete.all": "전체 삭제", + "refresh": "새로고침", + "contextmenu.roof.material.placement": "지붕재 배치", + "contextmenu.roof.material.edit": "지붕재 변경", + "contextmenu.roof.material.remove": "지붕재 삭제", + "contextmenu.roof.material.remove.all": "지붕재 전체 삭제", + "contextmenu.dormer.offset": "도머 오프셋", + "contextmenu.select.move": "선택・이동", + "contextmenu.wallline.remove": "외벽선 삭제", + "contextmenu.size.edit": "사이즈 변경", + "contextmenu.auxiliary.move": "보조선 이동", + "contextmenu.auxiliary.copy": "보조선 복사", + "contextmenu.auxiliary.remove": "보조선 삭제", + "contextmenu.auxiliary.vertical.bisector": "보조선 수직이등분선", + "contextmenu.auxiliary.cut": "보조선 절삭", + "contextmenu.auxiliary.remove.all": "보조선 전체 삭제", + "contextmenu.line.property.edit": "각 변 속성 변경", + "modal.line.property.edit.info": "속성을 변경할 변을 선택해주세요.", + "modal.line.property.edit.selected": "선택한 값", + "contextmenu.flow.direction.edit": "흐름 방향 변경", + "contextmenu.font.setting": "폰트 설정", + "contextmenu.grid.color.edit": "그리드 색 변경", + "contextmenu.dimension.auxiliary.line.edit": "치수 보조선 변경", + "contextmenu.display.edit": "표시 변경", + "contextmenu.opening.offset": "개구 오프셋", + "contextmenu.remove": "삭제", + "contextmenu.remove.all": "전체 삭제", + "contextmenu.move": "이동", + "contextmenu.copy": "복사", + "contextmenu.edit": "편집", "common.message.no.data": "No data", "common.message.no.dataDown": "No data to download", "common.message.noData": "No data to display", @@ -381,6 +425,7 @@ "board.sub.total": "전체", "board.sub.fileList": "첨부파일 목록", "board.sub.updDt": "업데이트", + "board.sub.btn.close": "닫기", "myinfo.title": "My profile", "myinfo.info.userId": "사용자ID", "myinfo.info.nameKana": "담당자명 후리가나", @@ -450,6 +495,8 @@ "join.complete.title": "Q.CAST3 로그인ID 발행신청 완료", "join.complete.contents": "※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.", "join.complete.email_comment": "담당자 이메일 주소", + "join.validation.check1": "{0} 형식을 확인해주세요.", + "join.complete.save.confirm": "Hanwha Japan 담당자에게 ID승인이 요청되면 더 이상 정보를 수정할 수 없습니다. 정말로 요청하시겠습니까?", "stuff.gridHeader.lastEditDatetime": "갱신일시", "stuff.gridHeader.objectNo": "물건번호", "stuff.gridHeader.planTotCnt": "플랜 수", @@ -491,6 +538,7 @@ "stuff.detail.saleStoreId": "1차 판매점명 / ID", "stuff.detail.otherSaleStoreId": "2차 판매점명 / ID", "stuff.detail.zipNo": "우편번호", + "stuff.detail.address": "주소", "stuff.detail.btn.addressPop": "주소검색", "stuff.detail.btn.addressPop.guide": "※ 주소검색 버튼을 클릭한 후, 도도부현 정보를 선택해주십시오.", "stuff.detail.prefId": "도도부현 / 주소", @@ -589,6 +637,7 @@ "stuff.detail.planGridHeader.management": "관리", "stuff.detail.planGrid.btn1": "견적서 조회", "stuff.detail.planGrid.btn2": "Excel", + "stuff.grid.noData": "조회된 데이터가 없습니다", "length": "길이", "height": "높이", "output": "출력", @@ -600,9 +649,23 @@ "size": "치수", "size.angle": "寸(度)", "eaves": "처마", + "eaves.line": "처마선", "gable": "케라바", + "gable.left": "케라바 왼쪽", + "gable.right": "케라바 오른쪽", + "ridge": "용마루", + "oneside.flow.ridge": "한쪽흐름 용마루", + "yosemune": "요세무네", + "valley": "골짜기", + "l.abandon.valley": "L의 버림 계곡", + "mansard": "멘사드", "wall": "벽", "wall.merge": "벽취합", + "wall.merge.type": "벽취합(형)", + "wall.merge.flow": "벽취합(흐름)", + "wall.merge.flow.left": "벽취합(흐름 왼쪽)", + "wall.merge.flow.right": "벽취합(흐름 오른쪽)", + "no.setting": "설정없음", "hajebichi": "하제비치", "straight.line": "직선", "right.angle": "직각", diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index d9878f2f..2eade854 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -1,6 +1,8 @@ import { atom, selector } from 'recoil' import { MENU } from '@/common/common' import { outerLineFixState, outerLinePointsState } from '@/store/outerLineAtom' +import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' +import { basicSettingState } from '@/store/settingAtom' export const canvasState = atom({ key: 'canvasState', @@ -275,6 +277,11 @@ export const modifiedPlansState = atom({ key: 'modifiedPlansState', default: [], }) +// 변경감지 flag +export const modifiedPlanFlagState = atom({ + key: 'modifiedPlanFlagState', + default: false, +}) export const tempGridModeState = atom({ key: 'tempGridModeState', @@ -300,3 +307,49 @@ export const globalPitchState = atom({ key: 'globalPitch', default: 4, }) + +export const pitchSelector = selector({ + key: 'pitchSelector', + get: ({ get }) => { + const globalPitch = get(globalPitchState) + const basicSettingStateValue = get(basicSettingState) + const roofAngleSet = basicSettingStateValue.roofAngleSet + if (roofAngleSet === 'slope') { + return globalPitch + } else { + return getDegreeByChon(globalPitch) + } + }, + set: ({ get, set }, newValue) => { + const basicSettingStateValue = get(basicSettingState) + const roofAngleSet = basicSettingStateValue.roofAngleSet + console.log(newValue) + if (roofAngleSet === 'slope') { + set(globalPitchState, newValue) + } else { + set(globalPitchState, getChonByDegree(newValue)) + } + }, +}) + +export const ANGLE_TYPE = { + SLOPE: 'slope', + FLAT: 'flat', +} + +export const currentAngleTypeSelector = selector({ + key: 'currentAngleTypeSelector', + get: ({ get }) => { + const basicSettingStateValue = get(basicSettingState) + return basicSettingStateValue.roofAngleSet + }, +}) + +export const pitchTextSelector = selector({ + key: 'pitchTextSelector', + get: ({ get }) => { + const basicSettingStateValue = get(basicSettingState) + const roofAngleSet = basicSettingStateValue.roofAngleSet + return roofAngleSet === 'slope' ? '寸' : '度' + }, +}) diff --git a/src/store/contextMenu.js b/src/store/contextMenu.js new file mode 100644 index 00000000..31b18f53 --- /dev/null +++ b/src/store/contextMenu.js @@ -0,0 +1,11 @@ +import { atom } from 'recoil' + +export const contextMenuState = atom({ + key: 'contextMenuState', + default: { + visible: false, + x: 0, + y: 0, + }, + dangerouslyAllowMutability: true, +}) diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js index c6e94029..2432d884 100644 --- a/src/store/settingAtom.js +++ b/src/store/settingAtom.js @@ -157,3 +157,23 @@ export const roofDisplaySelector = selector({ }, dangerouslyAllowMutability: true, }) + +export const basicSettingState = atom({ + key: 'basicSettingState', + default: { + roofSizeSet: 1, + roofAngleSet: 'slope', + roofs: [ + { + roofApply: true, + roofSeq: 1, + roofType: 1, + roofWidth: 200, + roofHeight: 200, + roofHajebichi: 200, + roofGap: 0, + roofLayout: 'parallel', + }, + ], + }, +}) diff --git a/src/styles/_canvasside.scss b/src/styles/_canvasside.scss index 66bba83d..efb79f02 100644 --- a/src/styles/_canvasside.scss +++ b/src/styles/_canvasside.scss @@ -4,30 +4,28 @@ top: 200px; left: 50px; z-index: 999999; + display: flex; width: 237px; height: 40px; line-height: 40px; background-color: #fff; border: 1px solid #DFDFDF; - padding: 0 34px 0 10px; + padding: 0 10px 0 10px; border-radius: 2px; box-shadow: 0px 7px 14px 0px rgba(0, 0, 0, 0.05); cursor: pointer; - &::before{ - content: ''; - position: absolute; - top: 50%; - right: 12px; - transform: translateY(-50%); - width: 10px; - height: 6px; + .penal-arr{ + flex: none; + width: 24px; + height: 100%; background: url(../../public/static/images/canvas/penal_arr.svg)no-repeat center; - background-size: cover; + background-size: 10px 6px; } h2{ font-size: 12px; font-weight: 500; color: #3D3D3D; + flex: 1; } .penal-table-wrap{ display: none; @@ -69,7 +67,7 @@ h2{ color: #fff; } - &::before{ + .penal-arr{ background: url(../../public/static/images/canvas/penal_arr_white.svg)no-repeat center; } .penal-table-wrap{ diff --git a/src/styles/_contents.scss b/src/styles/_contents.scss index c242a96b..e3d8d1ee 100644 --- a/src/styles/_contents.scss +++ b/src/styles/_contents.scss @@ -679,7 +679,6 @@ .infomation-box-wrap{ display: flex; - align-items: center; gap: 10px; .sub-table-box{ flex: 1 ; diff --git a/src/styles/_grid-detail.scss b/src/styles/_grid-detail.scss index 048a1946..0392bae8 100644 --- a/src/styles/_grid-detail.scss +++ b/src/styles/_grid-detail.scss @@ -8,6 +8,8 @@ --ag-header-height: 40px; --ag-header-foreground-color: white; --ag-header-background-color: #5D6A76; + --ag-row-hover-color: #ECF0F4; + // --ag-header-cell-hover-background-color: rgb(80, 40, 140); --ag-header-cell-moving-background-color: #5D6A76; .ag-root-wrapper{ @@ -41,8 +43,16 @@ } } .ag-cell{ + display: flex; + align-items: center; font-size: 13px; color: #45576F; + line-height: 1.4 !important; + padding-top: 10px; + padding-bottom: 10px; + .block{ + display: block; + } } .ag-icon-desc::before, .ag-icon-asc::before, @@ -94,7 +104,6 @@ justify-content: center; background-color: #fff; border: 1px solid #94A0AD; - background-color: transparent; border-radius: 2px; font-size: 13px; color: #94A0AD; diff --git a/src/styles/_main.scss b/src/styles/_main.scss index b8783c3b..ea1535b1 100644 --- a/src/styles/_main.scss +++ b/src/styles/_main.scss @@ -252,7 +252,6 @@ .faq-item{ position: relative; margin-bottom: 10px; - cursor: pointer; .faq-item-inner{ display: flex; align-items: center; diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss index b623dbff..525c0488 100644 --- a/src/styles/_reset.scss +++ b/src/styles/_reset.scss @@ -482,6 +482,9 @@ input[type=text]{ } &:read-only{ color: #AAA; + &:focus{ + border: 1px solid #323234; + } } &.plane{ font-family: 'Noto Sans JP', sans-serif; @@ -509,6 +512,9 @@ input[type=text]{ &:read-only{ background-color: #FAFAFA; color: #999999; + &:focus{ + border-color: #eee; + } } } } diff --git a/src/styles/_submodal.scss b/src/styles/_submodal.scss index e5fae9fb..de01ee36 100644 --- a/src/styles/_submodal.scss +++ b/src/styles/_submodal.scss @@ -316,11 +316,24 @@ } } .community_detail-inner{ - padding-top: 20px; - padding-bottom: 20px; + max-height: 300px; + overflow-y: auto; + margin-top: 20px; + margin-bottom: 20px; font-size: 13px; font-weight: 400; color: #45576F; line-height: 26px; + word-break: keep-all; + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #C1CCD7; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } } } \ No newline at end of file diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 8ed918ca..9f8b9469 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -262,6 +262,15 @@ export const getDegreeByChon = (chon) => { return Number((radians * (180 / Math.PI)).toFixed(2)) } +/** + * + */ +export const getChonByDegree = (degree) => { + // tan(theta) = height / base + const radians = (degree * Math.PI) / 180 + return Number(Number(Math.tan(radians) * 10).toFixed(1)) +} + /** * 두 점 사이의 방향을 반환합니다. * @param a {fabric.Object} diff --git a/src/util/common-utils.js b/src/util/common-utils.js index 9244b046..998eabd4 100644 --- a/src/util/common-utils.js +++ b/src/util/common-utils.js @@ -66,3 +66,23 @@ export const convertNumberToPriceDecimal = (value) => { else if (value === 0) return 0 else return '' } + +// 전화번호, FAX 번호 숫자 or '-'만 입력 체크 +export const inputTelNumberCheck = (e) => { + const input = e.target + if (/^[\d-]*$/g.test(input.value)) { + input.value = input.value + } else { + input.value = input.value.replace(/[^\d-]/g, '') + } +} + +// 숫자만 입력 체크 +export const inputNumberCheck = (e) => { + const input = e.target + if (/^[\d]*$/g.test(input.value)) { + input.value = input.value + } else { + input.value = input.value.replace(/[^\d]/g, '') + } +} diff --git a/yarn.lock b/yarn.lock index bf4e65b8..6e7b5f2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5884,6 +5884,11 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-loading-skeleton@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz#da2090355b4dedcad5c53cb3f0ed364e3a76d6ca" + integrity sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ== + react-onclickoutside@^6.13.0: version "6.13.1" resolved "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz"