diff --git a/src/app/community/qna/page.jsx b/src/app/community/qna/page.jsx new file mode 100644 index 00000000..90e98472 --- /dev/null +++ b/src/app/community/qna/page.jsx @@ -0,0 +1,9 @@ +import Qna from '@/components/community/Qna' + +export default async function CommunityQnaPage() { + return ( + <> + + + ) +} diff --git a/src/common/common.js b/src/common/common.js index c98cf385..0fe0a002 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -208,6 +208,8 @@ export const SAVE_KEY = [ 'fontWeight', 'dormerAttributes', 'toFixed', + 'startPoint', + 'endPoint', 'isSortedPoints', ] diff --git a/src/components/community/Qna.jsx b/src/components/community/Qna.jsx new file mode 100644 index 00000000..ed65637f --- /dev/null +++ b/src/components/community/Qna.jsx @@ -0,0 +1,214 @@ +'use client' + +import Link from 'next/link' +import Image from 'next/image' + +import Search from '@/components/community/Search' +import Pagination from '@/components/community/Pagination' + +import { useContext } from 'react' + +import { useEffect, useState } from 'react' +import { useResetRecoilState, useRecoilValue, useRecoilState } from 'recoil' +import { useMessage } from '@/hooks/useMessage' + +import { searchState } from '@/store/boardAtom' + +import { QcastContext } from '@/app/QcastProvider' +import QnaBoardDetailModal from '@/components/community/modal/QnaDetailModal' +import { sessionStore } from '@/store/commonAtom' +import { useAxios } from '@/hooks/useAxios' +import { useCommonCode } from '@/hooks/common/useCommonCode' + +export default function Qna() { + const { getMessage } = useMessage() + const resetSearch = useResetRecoilState(searchState) + const [isInitialized, setIsInitialized] = useState(false) + + //const search = useRecoilValue(searchState) + const [searchForm, setSearchForm] = useRecoilState(searchState) + const { findCommonCode } = useCommonCode() + const { setIsGlobalLoading } = useContext(QcastContext) + const { get } = useAxios() + const [boardList, setBoardList] = useState([]) + const [sessionState, setSessionState] = useRecoilState(sessionStore) + const [search, setSearch] = useRecoilState(searchState) + // 팝업 관련 + const [open, setOpen] = useState(false) + const [modalQnaNo, setModalQnaNo] = useState('') + const [modalQnaType, setModalQnaType] = useState('') + + // 목록 조회 + useEffect(() => { + async function fetchData() { + setIsGlobalLoading(true) + const startRow = (search.currentPage - 1) * search.pageBlock > 0 ? (search.currentPage - 1) * search.pageBlock + 1 : 1 + const endRow = search.currentPage * search.pageBlock + + const url = `/api/board/list` + const params = new URLSearchParams({ + schNoticeTpCd : 'QC', + schNoticeClsCd: 'QNA', + compCd : 5200, + storeId : sessionState.storeId, + loginId : sessionState.userId, + schTitle : search.searchValue ? search.searchValue : '', + startRow : startRow, + endRow : endRow, + schMainYn : 'N', + siteTpCd : 'QC', + }) + const apiUrl = `${url}?${params.toString()}` + + const resultData = await get({ url: apiUrl }) + + if (resultData) { + if (resultData.result.code === 200) { + if (resultData.data.length > 0) { + setBoardList(resultData.data) + setSearch({ ...search, totalCount: resultData.data[0].totCnt }) + } else { + setBoardList([]) + setSearch({ ...search, totalCount: 0 }) + } + } else { + alert(resultData.result.message) + } + } + setIsGlobalLoading(false) + } + + fetchData() + }, [search.currentPage, search.searchValue, search.pageBlock, search.searchFlag]) + + + useEffect(() => { + if (search.mainFlag === 'N') { + resetSearch() + } else { + //메인에서 FAQ 조회 왔을때 로딩바 해제 + setIsGlobalLoading(false) + setSearchForm({ ...searchForm, mainFlag: 'N' }) + } + setIsInitialized(true) + + //문의구분코드 + // const codeL = findCommonCode(204200) + // const codeM = findCommonCode(204300) + // const codeS = findCommonCode(204400) + + + + }, []) + + if (!isInitialized) { + return null + } + + const boardType = { + boardTitle: getMessage('qna.title'), + subTitle: getMessage('qna.sub.title'), + clsCode: 'QNA', + } + + + return ( + <> +
+
+
    +
  • + + {getMessage('qna.title')} + +
  • +
+
    +
  • + + react + +
  • +
  • + {getMessage('header.menus.community')} +
  • +
  • + {getMessage('qna.title')} +
  • +
+
+
+
+
+
+ + {/**/} +
+ + + + + + + + + + {boardList.length > 0 ? ( + boardList?.map((board) => ( + { + setOpen(true) + setModalQnaNo(board.qnaNo) + setModalQnaType("["+board?.qnaClsLrgCd+"/"+board?.qnaClsMidCd+"/"+board?.qnaClsSmlCd+"]") + }} + > + + {/* 답변 */} + {board?.answerYn === 'Y'? () : ()} + + + + + )) + ) : ( + + + + )} + +
+ {/* 번호 */} + {board.totCnt - board.rowNumber + 1} + {getMessage('qna.list.header.answer.yes')} {getMessage('qna.list.header.answer.no')} +
[{board?.qnaClsLrgCd} / {board?.qnaClsMidCd} / {board?.qnaClsSmlCd}]
+ {/* 제목 */} +
+
{board.title}{board.qstTitle}
+ {board.attachYn === 'Y' && } +
+
+
+ {/*{board.uptDt && (*/} + {/* <>*/} + {/* ({getMessage('board.uptDt')} : {board.uptDt})*/} + {/* */} + {/*)}*/} + {board.regUserNm} +
+
+ {/* 등록일 */} + {board.regDt.split(' ')[0]} +
+ {getMessage('common.message.no.data')} +
+
+ + +
+
+
+ {open && } + + ) +} diff --git a/src/components/community/Search.jsx b/src/components/community/Search.jsx index 26ca1883..a5916d37 100644 --- a/src/components/community/Search.jsx +++ b/src/components/community/Search.jsx @@ -4,10 +4,11 @@ import { searchState } from '@/store/boardAtom' import { useRecoilState, useRecoilValue } from 'recoil' import { useState } from 'react' import { useMessage } from '@/hooks/useMessage' +import QnaRegModal from '@/components/community/modal/QnaRegModal' -export default function Search({ title = '', subTitle = '', isSelectUse = false }) { +export default function Search({ title = '', subTitle = '', isSelectUse = false, clsCode = '' }) { const { getMessage } = useMessage() - + const [open, setOpen] = useState(false) const search = useRecoilValue(searchState) const [searchForm, setSearchForm] = useRecoilState(searchState) @@ -32,7 +33,13 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false } else { setSearchView(false) setSearchViewText('') - setSearchForm({ ...searchForm, currentPage: 1, searchValue: '', pageBlock: block, searchFlag: !searchForm.searchFlag }) + setSearchForm({ + ...searchForm, + currentPage: 1, + searchValue: '', + pageBlock : block, + searchFlag : !searchForm.searchFlag, + }) } // 조회 후 값 비워주기 setSearchValue('') @@ -57,7 +64,10 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false onKeyDown={handleKeyDown} value={searchValue} /> - + + + {searchView && (
@@ -92,6 +102,14 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
{isSelectUse && (
+ {clsCode === 'QNA' && +
+ +
+ }
onChangeFiles(e)} /> +
+
+
+
handleDrop(e)} + onDragOver={(e) => handleDragOver(e)} + onDragEnd={(e) => handleDragEnd(e)} + onDragLeave={(e) => handleDragLeave(e)}> +

Drag file here

+
    + {uploadFiles.length > 0 && + uploadFiles.map((file) => ( +
  • + + {file.data.name} + +
  • + ))} +
+
+
+ + + ) +} diff --git a/src/components/community/modal/QnaRegModal.jsx b/src/components/community/modal/QnaRegModal.jsx new file mode 100644 index 00000000..ef40457b --- /dev/null +++ b/src/components/community/modal/QnaRegModal.jsx @@ -0,0 +1,414 @@ +'use client' + +import { useMessage } from '@/hooks/useMessage' +import { sessionStore } from '@/store/commonAtom' +import { useRecoilState, useRecoilValue } from 'recoil' +import QnaFileUploader from '@/components/community/modal/QnaFileUploader' +import { useContext, useEffect, useRef, useState } from 'react' +import { useCommonCode } from '@/hooks/common/useCommonCode' +import Select from 'react-select' +import dayjs from 'dayjs' +import { useSwal } from '@/hooks/useSwal' +import { QcastContext } from '@/app/QcastProvider' +import { useAxios } from '@/hooks/useAxios' +import { globalLocaleStore } from '@/store/localeAtom' +import { e } from 'mathjs' + + +export default function QnaRegModal({ setOpen, setReload, searchValue, selectPageBlock }) { + const { getMessage } = useMessage() + const [fileList, setFileList] = useState([]) + const [sessionState, setSessionState] = useRecoilState(sessionStore) + const globalLocaleState = useRecoilValue(globalLocaleStore) + const [files, setFiles] = useState([]) + const [qnaData, setQnaData] = useState([]) + const [closeMdFlg, setCloseMdFlg] = useState(true) + const [closeSmFlg, setCloseSmFlg] = useState(true) + const qnaTypeLgCodeRef = useRef(null) + const qnaTypeMdCodeRef = useRef(null) + const qnaTypeSmCodeRef = useRef(null) + const regUserNmRef = useRef(null) + const regUserTelNoRef = useRef(null) + const titleRef = useRef(null) + const contentsRef = useRef(null) + const { findCommonCode } = useCommonCode() + const [qnaTypeLgCodeList, setQnaTypeLgCodeList] = useState([]) + const [qnaTypeMdCodeList, setQnaTypeMdCodeList] = useState([]) + const [qnaTypeSmCodeList, setQnaTypeSmCodeList] = useState([]) + const [phoneNumber, setPhoneNumber] = useState(""); + const { swalFire } = useSwal() + const { setIsGlobalLoading } = useContext(QcastContext) + const [isBtnDisable, setIsBtnDisable] = useState(false); + const { promiseGet, post, promisePost } = useAxios(globalLocaleState) + +let fileCheck = false; + const regPhoneNumber = (e) => { + const result = e.target.value + .replace(/[^0-9.]/g, "") + //.replace(/^(\d{0,3})(\d{0,4})(\d{0,4})$/g, "$1-$2-$3") + //.replace(/(-{1,2})$/g, ""); + //setPhoneNumber(result); + + setQnaData({...qnaData, regUserTelNo: result }) + } + + const fileUploadProps = { + uploadFiles: files, + setUploadFiles: setFiles, + + } + + // const fileSave = (qnaData, fileUploadProps) => { + // return qnaData.files.push(fileUploadProps.uploadFiles) + // } + + const initQnaReg = async () => { + + + regUserNmRef.current.value = '' + regUserTelNoRef.current.value = '' + qnaTypeLgCodeRef.current.setValue(); + qnaTypeMdCodeRef.current.setValue(); + qnaTypeSmCodeRef.current.setValue(); + titleRef.current.value = '' + contentsRef.current.value = '' + + //setQnaData([]) + + setQnaData({ + ...qnaData, + compCd: "5200", + siteTpCd: "QC", + schNoticeClsCd: "QNA", + regId: sessionState.userId, + storeId: sessionState.userId, + qstMail : sessionState.email + }) + + const codeL = findCommonCode(204200) + if (codeL != null) { + setQnaTypeLgCodeList(codeL) + } + setIsGlobalLoading(false) + setIsBtnDisable(false); + } + const onChangeQnaTypeL = (e) => { + if(e === undefined || e === null) return; + const codeM = findCommonCode(204300) + if (codeM != null) { + + let codeList = [] + + codeM.map((item) => { + + if(item.clRefChr1 === e.clCode) { + codeList.push(item); + + } + }) + setQnaTypeMdCodeList(codeList) + setQnaData({ ...qnaData, qnaClsLrgCd:e.clCode}) + setCloseMdFlg(false) + qnaTypeMdCodeRef.current.setValue(); + qnaTypeSmCodeRef.current.setValue(); + } + + } + const onChangeQnaTypeM = (e) => { + + if(e === undefined || e === null) return; + const codeS = findCommonCode(204400) + if (codeS != null) { + + let codeList = [] + + codeS.map((item) => { + + if (item.clRefChr1 === e.clCode) { + codeList.push(item); + + } + }) + setQnaTypeSmCodeList(codeList) + setQnaData({ ...qnaData, qnaClsMidCd: e.clCode }) + setCloseSmFlg(false) + qnaTypeSmCodeRef.current.setValue(); + + } + + } + const onChangeQnaTypeS = (e) => { + if(e === undefined || e === null) return; + setQnaData({ ...qnaData, qnaClsSmlCd:e.clCode}) + } + + const onFileSave = () => { + const formData= [] + if(fileUploadProps.uploadFiles.length === 0) return; + if(!fileCheck) return; + + fileUploadProps.uploadFiles.forEach((file) => { + console.log("file::::::::",file) + formData.push(file) + + }) + setQnaData({ ...qnaData, files:formData }) + fileCheck = false; + } + + const handleQnaSubmit = async () => { + //필수 체크 + + //console.log("1::::",qnaData) + + + let regUserNm = qnaData?.regUserNm??''; + + if (regUserNm.trim().length === 0) { + + regUserNmRef.current.value = ''; + regUserNmRef.current.focus() + swalFire({ + text: getMessage('qna.reg.alert.require.regUserNm'), + type: 'alert', + }) + return false + } + + let qnaClsLrgCd = qnaData?.qnaClsLrgCd??''; + let qnaClsMidCd = qnaData?.qnaClsMidCd??''; + + if (qnaClsLrgCd.trim().length === 0 || qnaClsMidCd.trim().length === 0 ) { + (qnaClsLrgCd.trim().length === 0)?qnaTypeLgCodeRef.current.focus():qnaTypeMdCodeRef.current.focus() + swalFire({ + text: getMessage('qna.reg.alert.select.type'), + type: 'alert', + }) + return false + } + + let title = qnaData?.title??''; + + if (title.trim().length === 0) { + titleRef.current.value = ''; + titleRef.current.focus() + swalFire({ + text: getMessage('qna.reg.alert.require.title'), + type: 'alert', + }) + return false + } + //console.log("5::::",qnaData) + let contents = qnaData?.contents??''; + + if (contents.trim().length === 0) { + contentsRef.current.value = ''; + contentsRef.current.focus() + swalFire({ + text: getMessage('qna.reg.alert.require.contents'), + type: 'alert', + }) + return false + } + + const formData = new FormData() + if(qnaData?.files?.length > 0) { + qnaData?.files.forEach((file) => { + formData.append('files', file.data) + }) + } + formData.append("compCd", qnaData.compCd) + formData.append("siteTpCd", qnaData.siteTpCd) + formData.append("qnaClsLrgCd", qnaData.qnaClsLrgCd) + formData.append("qnaClsMidCd", qnaData.qnaClsMidCd) + formData.append("qnaClsSmlCd", qnaData.qnaClsSmlCd) + formData.append("title", qnaData.title) + formData.append("contents", qnaData.contents) + formData.append("regId", qnaData.regId) + formData.append("storeId", qnaData.storeId) + formData.append("regUserNm", qnaData.regUserNm) + formData.append("regUserTelNo", qnaData.regUserTelNo) + formData.append("qstMail", qnaData.qstMail) + formData.append("schNoticeClsCd", qnaData.schNoticeClsCd) + + + + //console.log(Array.from(formData)); + + swalFire({ + html: getMessage('qna.reg.confirm.save'), + type: 'confirm', + confirmFn: async () => { + + setIsBtnDisable(true); + setIsGlobalLoading(true) + + try { + + const apiUrl = 'api/board' + //console.log("7::::",qnaData) + await post({ url: `${apiUrl}/saveQna`, data: formData }).then((res) => { + if (res?.result.code === 200) { + //qnaData.newFileList = [] + setIsGlobalLoading(false) + swalFire({ text: getMessage('qna.reg.alert.save'), type: 'alert' }) + setOpen(false) + setReload(searchValue, selectPageBlock); + }else{ + setIsGlobalLoading(false) + swalFire({ text: getMessage('qna.reg.alert.saveFail'), type: 'alert', icon: 'error' }) + console.error('error::::::::::::', res) + } + + setIsBtnDisable(false) + }) + } catch (e) { + setIsGlobalLoading(false) + setIsBtnDisable(false); + console.error('error::::::::::::', e.message) + swalFire({ text: e.message, type: 'alert' , icon: 'error'}) + console.error('error::::::::::::', e.message) + } + } + }) + } + + useEffect(() => { + initQnaReg() + },[]) + + // useEffect(() => { + // onFileSave() + // + // }, [onFileSave]) + return ( +
+
+
+
+

{getMessage('qna.title')}

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{getMessage('qna.list.header.regNm')}E-Mail*{getMessage('qna.reg.header.regDt')}{dayjs(new Date()).format('YYYY-MM-DD')}
{getMessage('qna.reg.header.regUserNm')}* setQnaData({...qnaData, regUserNm: e.target.value })} + onBlur={(e) => setQnaData({ ...qnaData, regUserNm: e.target.value })} /> {getMessage('qna.reg.header.regUserTelNo')}
+
+
+
+
+
{getMessage("qna.reg.header.type")}, {getMessage("qna.reg.header.title")} *
+
+
+
+ onChangeQnaTypeM(e)} + getOptionLabel={(x) => x.clCodeNm} + getOptionValue={(x) => x.clCode} + isClearable={false} + isSearchable={false} + isDisabled={closeMdFlg} + defaultValue={''} + /> +
+
+ {setQnaData({ ...qnaData, title: e.target.value })}} + /> +
+
+
+
+
{getMessage("qna.reg.header.contents")} *
+
+
+ +
+
+ + +
+
+ {isBtnDisable === false && } + +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index a471b5be..7b817f8f 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -2,7 +2,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' -import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' +import { calculateAngle, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' @@ -29,12 +29,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.texts = [] this.hips = [] this.ridges = [] - this.connectRidges = [] this.cells = [] this.innerLines = [] this.children = [] this.separatePolygon = [] this.toFixed = options.toFixed ?? 1 + this.baseLines = [] + // this.colorLines = [] // 소수점 전부 제거 points.forEach((point) => { @@ -217,6 +218,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { * @param settingModalFirstOptions */ drawHelpLine(settingModalFirstOptions) { + /* innerLines 초기화 */ + this.innerLines.forEach((line) => { + this.canvas.remove(line) + }) + this.canvas.renderAll() + let textMode = 'plane' const dimensionDisplay = settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id @@ -246,12 +253,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED) // A형, B형 박공 지붕 - if ( + /* if ( (gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) || (gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type))) ) { drawGabledRoof(this.id, this.canvas, textMode) - } else if (hasShed) { + } else*/ + if (hasShed) { const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED) const areLinesParallel = function (line1, line2) { const angle1 = calculateAngle(line1.startPoint, line1.endPoint) diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index c98f686b..5aa0332e 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -12,7 +12,7 @@ import { usePlan } from '@/hooks/usePlan' import { useContextMenu } from '@/hooks/useContextMenu' import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize' import { currentMenuState } from '@/store/canvasAtom' -import { totalDisplaySelector } from '@/store/settingAtom' +import { roofMaterialsAtom, totalDisplaySelector } from '@/store/settingAtom' import { MENU, POLYGON_TYPE } from '@/common/common' import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' import { QcastContext } from '@/app/QcastProvider' @@ -30,10 +30,40 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useEvent } from '@/hooks/useEvent' import { compasDegAtom } from '@/store/orientationAtom' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' +import { useMasterController } from '@/hooks/common/useMasterController' import { hotkeyStore } from '@/store/hotkeyAtom' import { usePopup } from '@/hooks/usePopup' export default function CanvasFrame() { + const [roofMaterials, setRoofMaterials] = useRecoilState(roofMaterialsAtom) + const { getRoofMaterialList } = useMasterController() + useEffect(() => { + async function initRoofMaterial() { + if (roofMaterials.length !== 0) { + return + } + const { data } = await getRoofMaterialList() + + const roofLists = data.map((item, idx) => ({ + ...item, + id: item.roofMatlCd, + name: item.roofMatlNm, + selected: idx === 0, + index: idx, + nameJp: item.roofMatlNmJp, + length: item.lenBase && parseInt(item.lenBase), + width: item.widBase && parseInt(item.widBase), + raft: item.raftBase && parseInt(item.raftBase), + layout: ['ROOF_ID_SLATE', 'ROOF_ID_SINGLE'].includes(item.roofMatlCd) ? ROOF_MATERIAL_LAYOUT.STAIRS : ROOF_MATERIAL_LAYOUT.PARALLEL, + hajebichi: item.roofPchBase && parseInt(item.roofPchBase), + pitch: item.pitch ? parseInt(item.pitch) : 4, + angle: item.angle ? parseInt(item.angle) : 21.8, + })) + setRoofMaterials(roofLists) + } + initRoofMaterial() + }, []) const canvasRef = useRef(null) const { canvas } = useCanvas('canvas') const { canvasLoadInit, gridInit } = useCanvasConfigInitialize() diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index 4a4d72a2..281ae5a8 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -86,6 +86,26 @@ export default function PassivityCircuitAllocation(props) { .map((obj) => obj.circuitNumber), ), ] + + const surfaceList = targetModules.map((module) => { + return canvas.getObjects().filter((obj) => obj.id === canvas.getObjects().filter((obj) => obj.id === module)[0].surfaceId)[0] + }) + + if (surfaceList.length > 1) { + let surfaceType = {} + + surfaceList.forEach((surface) => { + surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface + }) + if (Object.keys(surfaceType).length > 1) { + swalFire({ + text: getMessage('module.circuit.fix.not.same.roof.error'), + type: 'alert', + icon: 'warning', + }) + return + } + } if (!circuitNumber || circuitNumber === 0) { swalFire({ text: getMessage('module.circuit.minimun.error'), diff --git a/src/components/floor-plan/modal/grid/GridCopy.jsx b/src/components/floor-plan/modal/grid/GridCopy.jsx index e1a6f9f9..805cf186 100644 --- a/src/components/floor-plan/modal/grid/GridCopy.jsx +++ b/src/components/floor-plan/modal/grid/GridCopy.jsx @@ -4,9 +4,11 @@ import { usePopup } from '@/hooks/usePopup' import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { useState } from 'react' -import { currentObjectState } from '@/store/canvasAtom' +import { canvasState, currentObjectState } from '@/store/canvasAtom' import { useGrid } from '@/hooks/common/useGrid' - +import { gridColorState } from '@/store/gridAtom' +import { gridDisplaySelector } from '@/store/settingAtom' +const GRID_PADDING = 5 export default function GridCopy(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition } = props @@ -15,9 +17,39 @@ export default function GridCopy(props) { const [length, setLength] = useState('0') const [arrow, setArrow] = useState(null) const currentObject = useRecoilValue(currentObjectState) - const { copy } = useGrid() + const canvas = useRecoilValue(canvasState) + const gridColor = useRecoilValue(gridColorState) + const isGridDisplay = useRecoilValue(gridDisplaySelector) const handleApply = () => { - copy(currentObject, ['↑', '←'].includes(arrow) ? +length * -1 : +length) + copy(currentObject, ['↑', '←'].includes(arrow) ? (+length * -1) / 10 : +length / 10) + } + + const copy = (object, length) => { + const lineStartX = object.direction === 'vertical' ? object.x1 + length : 0 + const lineEndX = object.direction === 'vertical' ? object.x2 + length : canvas.width + const lineStartY = object.direction === 'vertical' ? 0 : object.y1 + length + const lineEndY = object.direction === 'vertical' ? canvas.width : object.y1 + length + + const line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], { + stroke: gridColor, + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + strokeDashArray: [5, 2], + opacity: 0.3, + padding: GRID_PADDING, + direction: object.direction, + visible: isGridDisplay, + name: object.name, + }) + + canvas.add(line) + canvas.setActiveObject(line) + canvas.renderAll() } return ( diff --git a/src/components/floor-plan/modal/grid/GridMove.jsx b/src/components/floor-plan/modal/grid/GridMove.jsx index 4aa27851..52084e00 100644 --- a/src/components/floor-plan/modal/grid/GridMove.jsx +++ b/src/components/floor-plan/modal/grid/GridMove.jsx @@ -6,7 +6,6 @@ import { contextPopupPositionState } from '@/store/popupAtom' import { useCanvas } from '@/hooks/useCanvas' import { canvasState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' -import { useGrid } from '@/hooks/common/useGrid' import { useSwal } from '@/hooks/useSwal' import { set } from 'react-hook-form' @@ -17,7 +16,6 @@ export default function GridMove(props) { const { getMessage } = useMessage() const { closePopup } = usePopup() const { swalFire } = useSwal() - const { move } = useGrid() const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const [isAll, setIsAll] = useState(false) const [verticalSize, setVerticalSize] = useState('0') @@ -54,21 +52,31 @@ export default function GridMove(props) { .forEach((grid) => { move( grid, - arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize), - arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize), + arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10, + arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10, ) }) } else { move( currentObject, - arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize), - arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize), + arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10, + arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10, ) } canvas.renderAll() handleClose() } + const move = (object, x, y) => { + object.set({ + ...object, + x1: object.direction === 'vertical' ? object.x1 + x : 0, + x2: object.direction === 'vertical' ? object.x1 + x : canvas.width, + y1: object.direction === 'vertical' ? 0 : object.y1 + y, + y2: object.direction === 'vertical' ? canvas.height : object.y1 + y, + }) + } + const handleClose = () => { closePopup(id) } diff --git a/src/components/floor-plan/modal/movement/type/FlowLine.jsx b/src/components/floor-plan/modal/movement/type/FlowLine.jsx index 565a9ff8..3c23a30a 100644 --- a/src/components/floor-plan/modal/movement/type/FlowLine.jsx +++ b/src/components/floor-plan/modal/movement/type/FlowLine.jsx @@ -15,13 +15,18 @@ export default function FlowLine({ FLOW_LINE_REF }) { const currentObject = useRecoilValue(currentObjectState) const handleFocus = () => { if (currentObject === null) { + FLOW_LINE_REF.POINTER_INPUT_REF.current.value = '' FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' FLOW_LINE_REF.FILLED_INPUT_REF.current.blur() } } const handleInput = (e) => { - const value = e.target.value.replace(/^0+/, '') - setFilledInput(value.replace(/[^0-9]/g, '')) + const regex = /^-?\d*$/ + let value = e.target.value + if (!regex.test(value) && value !== '') return + if (value.startsWith('0') || value === '-0') return + + setFilledInput(value.replace(/[^0-9-]/g, '')) } return ( <> @@ -47,7 +52,7 @@ export default function FlowLine({ FLOW_LINE_REF }) {
- {/**/} + {}
diff --git a/src/components/floor-plan/modal/movement/type/Updown.jsx b/src/components/floor-plan/modal/movement/type/Updown.jsx index bcc45590..786c7017 100644 --- a/src/components/floor-plan/modal/movement/type/Updown.jsx +++ b/src/components/floor-plan/modal/movement/type/Updown.jsx @@ -15,6 +15,7 @@ export default function Updown({ UP_DOWN_REF }) { const currentObject = useRecoilValue(currentObjectState) const handleFocus = () => { if (currentObject === null) { + UP_DOWN_REF.POINTER_INPUT_REF.current.value = '' UP_DOWN_REF.FILLED_INPUT_REF.current.value = '' UP_DOWN_REF.FILLED_INPUT_REF.current.blur() } @@ -48,7 +49,7 @@ export default function Updown({ UP_DOWN_REF }) {
- {/**/} + {}
diff --git a/src/components/floor-plan/modal/setting01/GridOption.jsx b/src/components/floor-plan/modal/setting01/GridOption.jsx index b5b61acc..5931b561 100644 --- a/src/components/floor-plan/modal/setting01/GridOption.jsx +++ b/src/components/floor-plan/modal/setting01/GridOption.jsx @@ -105,6 +105,16 @@ export default function GridOption(props) { initEvent() }, [gridOptions]) + useEffect(() => { + return () => { + setAdsorptionPointAddMode(false) + setTempGridMode(false) + setTimeout(() => { + initEvent() + }, 100) + } + }, []) + const dotLineGridProps = { id: dotLineId, setIsShow: setShowDotLineGridModal, diff --git a/src/components/floor-plan/modal/setting01/SecondOption.jsx b/src/components/floor-plan/modal/setting01/SecondOption.jsx index f264edb2..d0acddb2 100644 --- a/src/components/floor-plan/modal/setting01/SecondOption.jsx +++ b/src/components/floor-plan/modal/setting01/SecondOption.jsx @@ -185,7 +185,7 @@ export default function SecondOption(props) { const onClickOption = async (item) => { let option4Data = settingModalSecondOptions?.option4 - let adsorpPointData = adsorptionPointMode.adsorptionPoint + let adsorpPointData = adsorptionPointMode //흡착범위 설정(단 건 선택) if ( @@ -203,11 +203,9 @@ export default function SecondOption(props) { //흡착점 범위 setAdsorptionRange(item.range) - - setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: adsorpPointData }) + setAdsorptionPointMode(adsorpPointData) } else if (item === 'adsorpPoint') { - setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: !adsorpPointData }) - adsorpPointData = !adsorpPointData + setAdsorptionPointMode(!adsorpPointData) } setSettingsData({ ...settingsData, option4: [...option4Data], adsorptionPoint: adsorpPointData }) @@ -257,7 +255,7 @@ export default function SecondOption(props) { }} > {getMessage('modal.canvas.setting.font.plan.absorption.point')} - {adsorptionPointMode.adsorptionPoint ? 'ON' : 'OFF'} + {adsorptionPointMode ? 'ON' : 'OFF'}
diff --git a/src/components/floor-plan/modal/setting01/SettingModal01.jsx b/src/components/floor-plan/modal/setting01/SettingModal01.jsx index 4e6f91ea..71c19a83 100644 --- a/src/components/floor-plan/modal/setting01/SettingModal01.jsx +++ b/src/components/floor-plan/modal/setting01/SettingModal01.jsx @@ -6,16 +6,22 @@ import WithDraggable from '@/components/common/draggable/WithDraggable' import SecondOption from '@/components/floor-plan/modal/setting01/SecondOption' import { useMessage } from '@/hooks/useMessage' import GridOption from '@/components/floor-plan/modal/setting01/GridOption' -import { canGridOptionSeletor } from '@/store/canvasAtom' -import { useRecoilValue } from 'recoil' +import { adsorptionPointAddModeState, canGridOptionSeletor, tempGridModeState } from '@/store/canvasAtom' +import { useRecoilState, useRecoilValue } from 'recoil' import { usePopup } from '@/hooks/usePopup' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { useTempGrid } from '@/hooks/useTempGrid' +import { settingModalGridOptionsState } from '@/store/settingAtom' +import { useEvent } from '@/hooks/useEvent' export default function SettingModal01(props) { const { id } = props const [buttonAct, setButtonAct] = useState(1) const { getMessage } = useMessage() const canGridOptionSeletorValue = useRecoilValue(canGridOptionSeletor) + const [gridOptions, setGridOptions] = useRecoilState(settingModalGridOptionsState) + const [tempGridMode, setTempGridMode] = useRecoilState(tempGridModeState) + const [adsorptionPointAddMode, setAdsorptionPointAddMode] = useRecoilState(adsorptionPointAddModeState) const { closePopup } = usePopup() const { @@ -71,9 +77,22 @@ export default function SettingModal01(props) { setButtonAct(num) } + const onClose = () => { + setTempGridMode(false) + setAdsorptionPointAddMode(false) + setGridOptions((prev) => { + const newSettingOptions = [...prev] + newSettingOptions[0].selected = false + newSettingOptions[2].selected = false + return [...newSettingOptions] + }) + + closePopup(id, true) + } + return ( - closePopup(id, true)} /> +