Merge branch 'dev' into dev-yj

# Conflicts:
#	src/components/floor-plan/CanvasFrame.jsx
#	src/hooks/useContextMenu.js
This commit is contained in:
yjnoh 2024-10-28 13:18:22 +09:00
commit 665d00c367
70 changed files with 1450 additions and 639 deletions

View File

@ -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",

View File

@ -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() {
<col />
</colgroup>
<tbody>
{/* 판매대리점명 */}
<tr>
<th>
{getMessage('join.sub1.storeQcastNm')} <span className="important">*</span>
@ -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}
/>
</div>
</td>
</tr>
{/* 판매대리점명 후리가나 */}
<tr>
<th>
{getMessage('join.sub1.storeQcastNmKana')} <span className="important">*</span>
@ -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}
/>
</div>
</td>
</tr>
{/* 우편번호/주소 */}
<tr>
<th>
{getMessage('join.sub1.postCd')}/{getMessage('join.sub1.addr')} <span className="important">*</span>
@ -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}
/>
</div>
<div className="input-wrap" style={{ width: '495px' }}>
@ -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}
/>
</div>
</div>
</td>
</tr>
{/* 전화번호 */}
<tr>
<th>
{getMessage('join.sub1.telNo')} <span className="important">*</span>
@ -145,13 +274,16 @@ export default function Join() {
type="text"
id="telNo"
name="telNo"
required
className="input-light"
placeholder={getMessage('join.sub1.telNo_placeholder')}
></input>
maxLength={15}
onChange={inputTelNumberCheck}
ref={telNoRef}
/>
</div>
</td>
</tr>
{/* FAX 번호 */}
<tr>
<th>
{getMessage('join.sub1.fax')} <span className="important">*</span>
@ -162,18 +294,21 @@ export default function Join() {
type="text"
id="fax"
name="fax"
required
className="input-light"
placeholder={getMessage('join.sub1.fax_placeholder')}
></input>
maxLength={15}
onChange={inputTelNumberCheck}
ref={faxRef}
/>
</div>
</td>
</tr>
{/* 법인번호 */}
<tr>
<th>{getMessage('join.sub1.bizNo')}</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="bizNo" name="bizNo" className="input-light" />
<input type="text" id="bizNo" name="bizNo" className="input-light" maxLength={15} onChange={inputTelNumberCheck} />
</div>
</td>
</tr>
@ -196,44 +331,49 @@ export default function Join() {
<col />
</colgroup>
<tbody>
{/* 담당자명 */}
<tr>
<th>
{getMessage('join.sub2.userNm')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="userNm" name="userNm" className="input-light" required />
<input type="text" id="userNm" name="userNm" className="input-light" maxLength={20} ref={userNmRef} />
</div>
</td>
</tr>
{/* 담당자명 후리가나 */}
<tr>
<th>{getMessage('join.sub2.userNmKana')}</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="userNmKana" name="userNmKana" className="input-light" />
<input type="text" id="userNmKana" name="userNmKana" maxLength={20} className="input-light" />
</div>
</td>
</tr>
{/* 신청 ID */}
<tr>
<th>
{getMessage('join.sub2.userId')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="userId" name="userId" className="input-light" required />
<input type="text" id="userId" name="userId" className="input-light" maxLength={20} ref={userIdRef} />
</div>
</td>
</tr>
{/* 이메일 주소 */}
<tr>
<th>
{getMessage('join.sub2.email')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="email" name="email" className="input-light" required />
<input type="text" id="email" name="email" className="input-light" maxLength={30} ref={emailRef} />
</div>
</td>
</tr>
{/* 전화번호 */}
<tr>
<th>
{getMessage('join.sub2.telNo')} <span className="important">*</span>
@ -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}
/>
</div>
</td>
</tr>
{/* FAX 번호 */}
<tr>
<th>
{getMessage('join.sub2.fax')} <span className="important">*</span>
@ -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}
/>
</div>
</td>
</tr>
{/* 부서명 */}
<tr>
<th>{getMessage('join.sub2.category')}</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="category" name="category" className="input-light" />
<input type="text" id="category" name="category" className="input-light" maxLength={20} />
</div>
</td>
</tr>

View File

@ -331,7 +331,15 @@ export default function Login() {
></button>
</div>
<div className="pwreset-btn-box">
<button type="button" className="login-btn light mr5" onClick={() => setPasswordReset(1)}>
<button
type="button"
className="login-btn light mr5"
onClick={() => {
setPasswordReset(1)
setCheckEmail('')
setCheckId('')
}}
>
{getMessage('login.init_password.btn.back')}
</button>
<button type="button" className="login-btn" onClick={initPasswordProcess}>

View File

@ -1,15 +1,13 @@
'use client'
import { useEffect, useState } from 'react'
import { useEffect } from 'react'
import '@/styles/contents.scss'
import { useRecoilState } from 'recoil'
import { contextMenuState } from '@/store/contextMenu'
export default function QContextMenu(props) {
const { contextRef, canvasProps } = props
// const children = useRecoilValue(modalContent)
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 })
const { contextRef, canvasProps, handleKeyup } = props
const [contextMenu, setContextMenu] = useRecoilState(contextMenuState)
const activeObject = canvasProps?.getActiveObject() //
let contextType = ''
if (activeObject) {
@ -27,7 +25,7 @@ export default function QContextMenu(props) {
const handleContextMenu = (e) => {
// e.preventDefault() // contextmenu
setContextMenu({ visible: true, x: e.pageX, y: e.pageY })
// console.log(111, canvasProps)
document.addEventListener('keyup', (e) => handleKeyup(e))
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //
}

View File

@ -81,11 +81,11 @@ export default function Table({ clsCode }) {
{/* 번호 */}
{board.rowNumber}
</td>
<td style={{ textAlign: 'center' }}>
<td>
{/* 제목 */}
<div className="text-frame">
<div className="text-overflow">{board.title}</div>
{board.attachYn && <span className="clip"></span>}
{board.attachYn === 'Y' && <span className="clip"></span>}
</div>
</td>
<td className="al-c">

View File

@ -3,8 +3,11 @@
import { useEffect, useState } from 'react'
import { useAxios } from '@/hooks/useAxios'
import { handleFileDown } from '@/util/board-utils'
import { useMessage } from '@/hooks/useMessage'
export default function BoardDetailModal({ noticeNo, setOpen }) {
const { getMessage } = useMessage()
// api
const { get } = useAxios()
const [boardDetail, setBoardDetail] = useState({})
@ -46,7 +49,7 @@ export default function BoardDetailModal({ noticeNo, setOpen }) {
setOpen(false)
}}
>
닫기
{getMessage('board.sub.btn.close')}
</button>
</div>
<div className="modal-body">
@ -55,7 +58,7 @@ export default function BoardDetailModal({ noticeNo, setOpen }) {
{boardDetail.listFile && (
<dl className="community_detail-file-wrap">
<dt>첨부파일 목록</dt>
<dt>{getMessage('board.sub.fileList')}</dt>
{boardDetail.listFile.map((boardFile) => (
<dd key={boardFile.encodeFileNo}>
<button type="button" className="down" onClick={() => handleFileDown(boardFile)}>

View File

@ -12,7 +12,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
idx: 0,
area: 0,
children: [],
initialize: function (points, options, canvas) {
initialize: function (points, options, length = 0) {
this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? true })
if (options.id) {
this.id = options.id
@ -27,7 +27,11 @@ export const QLine = fabric.util.createClass(fabric.Line, {
this.idx = options.idx ?? 0
this.direction = options.direction ?? getDirectionByPoint({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
this.setLength()
if (length !== 0) {
this.length = length
} else {
this.setLength()
}
this.startPoint = { x: this.x1, y: this.y1 }
this.endPoint = { x: this.x2, y: this.y2 }
@ -148,7 +152,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
getLength() {
//10배 곱해진 값 return
return Number(this.length.toFixed(0) * 10)
return Number(this.length.toFixed(2) * 10)
},
setViewLengthText(bool) {

View File

@ -172,7 +172,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
addLengthText() {
this.canvas
?.getObjects()
.filter((obj) => obj.name === 'lengthText' && obj.parent === this)
.filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
.forEach((text) => {
this.canvas.remove(text)
})
@ -183,7 +183,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
const end = points[(i + 1) % points.length]
const dx = end.x - start.x
const dy = end.y - start.y
const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) * 10
const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(2)) * 10
let midPoint

View File

@ -2,34 +2,29 @@
import { useEffect, useRef } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useCanvas } from '@/hooks/useCanvas'
import { useEvent } from '@/hooks/useEvent'
import { usePlan } from '@/hooks/usePlan'
import { useContextMenu } from '@/hooks/useContextMenu'
import { useRecoilValue } from 'recoil'
import { currentObjectState } from '@/store/canvasAtom'
import { currentMenuState, currentObjectState, modifiedPlanFlagState } from '@/store/canvasAtom'
import { useCanvasEvent } from '@/hooks/useCanvasEvent'
import QContextMenu from '@/components/common/context-menu/QContextMenu'
import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize'
import { useCommonUtils } from '@/hooks/common/useCommonUtils'
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
import { MENU } from '@/common/common'
import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/PanelBatchStatistics'
export default function CanvasFrame({ plan }) {
const canvasRef = useRef(null)
const [modifiedPlanFlag, setModifiedPlanFlag] = useRecoilState(modifiedPlanFlagState)
const { canvas } = useCanvas('canvas')
const { handleZoomClear } = useCanvasEvent()
const { testAlert } = useCommonUtils({})
const { contextMenu, currentContextMenu, setCurrentContextMenu, handleClick } = useContextMenu({
externalFn: {
handleZoomClear,
},
})
const { checkCanvasObjectEvent, checkUnsavedCanvasPlan } = usePlan()
const { canvasLoadInit } = useCanvasConfigInitialize()
const currentObject = useRecoilValue(currentObjectState)
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
const currentMenu = useRecoilValue(currentMenuState)
const { contextMenu, handleClick, handleKeyup } = useContextMenu()
const { checkCanvasObjectEvent, resetModifiedPlans } = usePlan()
useEvent()
const loadCanvas = () => {
@ -37,42 +32,45 @@ export default function CanvasFrame({ plan }) {
canvas?.clear() // .
if (plan?.canvasStatus) {
canvas?.loadFromJSON(JSON.parse(plan.canvasStatus), function () {
canvas?.renderAll() // .
canvasLoadInit() //config
canvas?.renderAll() // .
})
}
gridInit()
}
}
useEffect(() => {
loadCanvas()
}, [plan, canvas])
if (modifiedPlanFlag && plan?.id) {
checkCanvasObjectEvent(plan.id)
}
}, [modifiedPlanFlag])
const onClickContextMenu = (index) => {}
useEffect(() => {
loadCanvas()
resetModifiedPlans()
}, [plan, canvas])
return (
<div className="canvas-frame">
<canvas ref={canvasRef} id="canvas" style={{ position: 'relative' }}></canvas>
<QContextMenu contextRef={canvasRef} canvasProps={canvas}>
<QContextMenu contextRef={canvasRef} canvasProps={canvas} handleKeyup={handleKeyup}>
{contextMenu.map((menus, index) => (
<ul key={index}>
{menus.map((menu) => (
<li
key={menu.id}
onClick={(e) => {
if (menu.fn) {
menu.fn()
}
handleClick(e, menu)
}}
>
<li key={menu.id} onClick={(e) => handleClick(e, menu)}>
{menu.name}
</li>
))}
</ul>
))}
</QContextMenu>
{[
MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING,
MENU.MODULE_CIRCUIT_SETTING.CIRCUIT_TRESTLE_SETTING,
MENU.MODULE_CIRCUIT_SETTING.PLAN_ORIENTATION,
].includes(currentMenu) && <PanelBatchStatistics />}
</div>
)
}

View File

@ -1,11 +1,12 @@
'use client'
import { useContext, useEffect, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { useRecoilState, useRecoilValue } from 'recoil'
import CanvasFrame from './CanvasFrame'
import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
import { usePlan } from '@/hooks/usePlan'
import { modifiedPlansState } from '@/store/canvasAtom'
import { globalLocaleStore } from '@/store/localeAtom'
import { SessionContext } from '@/app/SessionProvider'
@ -13,11 +14,12 @@ export default function CanvasLayout(props) {
const { menuNumber } = props
const { session } = useContext(SessionContext)
const [objectNo, setObjectNo] = useState('test123240822001') //
const [modifiedPlans, setModifiedPlans] = useRecoilState(modifiedPlansState) // canvas plan
const globalLocaleState = useRecoilValue(globalLocaleStore)
const { getMessage } = useMessage()
const { swalFire } = useSwal()
const { plans, modifiedPlans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan()
const { plans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan()
useEffect(() => {
loadCanvasPlanData(session.userId, objectNo)

View File

@ -1,6 +1,6 @@
import { useMessage } from '@/hooks/useMessage'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { globalPitchState } from '@/store/canvasAtom'
import { globalPitchState, pitchSelector, pitchTextSelector } from '@/store/canvasAtom'
import { useRecoilState } from 'recoil'
import { useRef } from 'react'
import { usePopup } from '@/hooks/usePopup'
@ -8,7 +8,8 @@ import { usePopup } from '@/hooks/usePopup'
export default function Slope({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [globalPitch, setGlobalPitch] = useRecoilState(globalPitchState)
const [globalPitch, setGlobalPitch] = useRecoilState(pitchSelector)
const pitchText = useRecoilState(pitchTextSelector)
const inputRef = useRef()
return (
@ -29,7 +30,7 @@ export default function Slope({ id, pos = { x: 50, y: 230 } }) {
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={globalPitch} ref={inputRef} />
</div>
<span className="thin">{getMessage('size.angle')}</span>
<span className="thin">{pitchText}</span>
</div>
</div>
<div className="grid-btn-wrap">

View File

@ -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 = {

View File

@ -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 (
<>
<div className="outline-wrap">
@ -17,9 +20,9 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef }) {
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="number" className="input-origin block" defaultValue={4} ref={pitchRef} />
<input type="number" className="input-origin block" defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8} ref={pitchRef} />
</div>
<span className="thin"></span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form">
<span className="mr10" style={{ width: '24px' }}>

View File

@ -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 (
<>
<div className="outline-wrap">
@ -57,9 +61,15 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef }) {
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={4.5} ref={pitchRef} readOnly={type === '1'} />
<input
type="text"
className="input-origin block"
defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4.5 : 20}
ref={pitchRef}
readOnly={type === '1'}
/>
</div>
<span className="thin"></span>
<span className="thin">{pitchText}</span>
</div>
</div>
<div className="eaves-keraba-td">

View File

@ -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 (
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap lx`}>
<div className="modal-head">
<h1 className="title">面フローの設定 </h1>
<h1 className="title">{getMessage('modal.shape.flow.direction.setting')} </h1>
<button className="modal-close" onClick={() => closePopup(id)}>
닫기
</button>
@ -26,113 +70,59 @@ export default function FlowDirectionSetting(props) {
<div className="modal-body">
<div className="drawing-flow-wrap">
<div className="discrimination-box">
<div className="discrimination-tit mb15">流れ方向の設定</div>
<div className="guide">流れ方向を選択してください</div>
<div className="discrimination-tit mb15">{getMessage('modal.flow.direction.setting')}</div>
<div className="guide">{getMessage('modal.flow.direction.setting.info')}</div>
<div className="object-direction-wrap">
<div className="plane-direction">
<span className="top"></span>
<span className="right">ドン</span>
<span className="bottom"></span>
<span className="left">立つ</span>
<span className="top">{getMessage('commons.north')}</span>
<button className="plane-btn up"></button>
<span className="right">{getMessage('commons.east')}</span>
<button className="plane-btn right"></button>
<span className="bottom">{getMessage('commons.south')}</span>
<button className="plane-btn down act"></button>
<span className="left">{getMessage('commons.west')}</span>
<button className="plane-btn left"></button>
</div>
</div>
</div>
<div className="discrimination-box">
<div className="discrimination-tit mb15">方位設定</div>
<div className="guide">シミュレーション計算の方向を指定します面が向いている方位を選択してください</div>
<div className="discrimination-tit mb15">{getMessage('modal.module.basic.setting.orientation.setting')}</div>
<div className="guide">{getMessage('modal.shape.flow.direction.setting.orientation.setting.info')}</div>
<div className="mb-box">
<div className="d-check-radio pop mb15">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01">8方位に選ぶ</label>
<input type="radio" name="radio01" id="ra01" value={0} checked={type === '0'} onChange={(e) => setType(e.target.value)} />
<label htmlFor="ra01">{getMessage('modal.shape.flow.direction.setting.orientation.8')}</label>
</div>
<div className="grid-select ">
<QSelectBox title={'M'} options={SelectOption01} />
<QSelectBox title={''} options={orientations} value={selectedOrientation} onChange={(e) => setSelectedOrientation(e)} />
</div>
</div>
<div className="mb-box">
<div className="d-check-radio pop">
<input type="radio" name="radio02" id="ra02" />
<label htmlFor="ra02">24方位から選択する (表記は8方位です)</label>
<input type="radio" name="radio01" id="ra02" value={1} checked={type === '1'} onChange={(e) => setType(e.target.value)} />
<label htmlFor="ra02">{getMessage('modal.shape.flow.direction.setting.orientation.24')}</label>
</div>
</div>
<div className="compas-box">
<div className="compas-box-inner">
<div className={`circle ${compasDeg === 180 ? 'act' : ''}`} onClick={() => setCompasDeg(180)}>
<i>13</i>
</div>
<div className={`circle ${compasDeg === 195 ? 'act' : ''}`} onClick={() => setCompasDeg(195)}>
<i>12</i>
</div>
<div className={`circle ${compasDeg === 210 ? 'act' : ''}`} onClick={() => setCompasDeg(210)}>
<i>11</i>
</div>
<div className={`circle ${compasDeg === 225 ? 'act' : ''}`} onClick={() => setCompasDeg(225)}>
<i>10</i>
</div>
<div className={`circle ${compasDeg === 240 ? 'act' : ''}`} onClick={() => setCompasDeg(240)}>
<i>9</i>
</div>
<div className={`circle ${compasDeg === 255 ? 'act' : ''}`} onClick={() => setCompasDeg(255)}>
<i>8</i>
</div>
<div className={`circle ${compasDeg === 270 ? 'act' : ''}`} onClick={() => setCompasDeg(270)}>
<i>7</i>
</div>
<div className={`circle ${compasDeg === 285 ? 'act' : ''}`} onClick={() => setCompasDeg(285)}>
<i>6</i>
</div>
<div className={`circle ${compasDeg === 300 ? 'act' : ''}`} onClick={() => setCompasDeg(300)}>
<i>5</i>
</div>
<div className={`circle ${compasDeg === 315 ? 'act' : ''}`} onClick={() => setCompasDeg(315)}>
<i>4</i>
</div>
<div className={`circle ${compasDeg === 330 ? 'act' : ''}`} onClick={() => setCompasDeg(330)}>
<i>3</i>
</div>
<div className={`circle ${compasDeg === 345 ? 'act' : ''}`} onClick={() => setCompasDeg(345)}>
<i>2</i>
</div>
<div className={`circle ${compasDeg === 0 ? 'act' : ''}`} onClick={() => setCompasDeg(0)}>
<i>1</i>
</div>
<div className={`circle ${compasDeg === 15 ? 'act' : ''}`} onClick={() => setCompasDeg(15)}>
<i>24</i>
</div>
<div className={`circle ${compasDeg === 30 ? 'act' : ''}`} onClick={() => setCompasDeg(30)}>
<i>23</i>
</div>
<div className={`circle ${compasDeg === 45 ? 'act' : ''}`} onClick={() => setCompasDeg(45)}>
<i>22</i>
</div>
<div className={`circle ${compasDeg === 60 ? 'act' : ''}`} onClick={() => setCompasDeg(60)}>
<i>21</i>
</div>
<div className={`circle ${compasDeg === 75 ? 'act' : ''}`} onClick={() => setCompasDeg(75)}>
<i>20</i>
</div>
<div className={`circle ${compasDeg === 90 ? 'act' : ''}`} onClick={() => setCompasDeg(90)}>
<i>19</i>
</div>
<div className={`circle ${compasDeg === 105 ? 'act' : ''}`} onClick={() => setCompasDeg(105)}>
<i>18</i>
</div>
<div className={`circle ${compasDeg === 120 ? 'act' : ''}`} onClick={() => setCompasDeg(120)}>
<i>17</i>
</div>
<div className={`circle ${compasDeg === 135 ? 'act' : ''}`} onClick={() => setCompasDeg(135)}>
<i>16</i>
</div>
<div className={`circle ${compasDeg === 150 ? 'act' : ''}`} onClick={() => setCompasDeg(150)}>
<i>15</i>
</div>
<div className={`circle ${compasDeg === 165 ? 'act' : ''}`} onClick={() => setCompasDeg(165)}>
<i>14</i>
</div>
{Array.from({ length: 180 / 15 + 1 }).map((dot, index) => (
<div
key={index}
className={`circle ${compasDeg === 15 * (12 + index) ? 'act' : ''}`}
onClick={() => setCompasDeg(15 * (12 + index))}
>
<i>{13 - index}</i>
</div>
))}
{Array.from({ length: 180 / 15 - 1 }).map((dot, index) => (
<div
key={index}
className={`circle ${compasDeg === 15 * (index + 1) ? 'act' : ''}`}
onClick={() => setCompasDeg(15 * (index + 1))}
>
<i>{24 - index}</i>
</div>
))}
<div className="compas">
<div className="compas-arr" style={{ transform: `rotate(${compasDeg}deg)` }}></div>
</div>
@ -141,7 +131,7 @@ export default function FlowDirectionSetting(props) {
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">保存</button>
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
</div>
</div>
</div>

View File

@ -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 (
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap r`}>
<div className={`modal-pop-wrap r mount`}>
<div className="modal-head">
<h1 className="title">各辺属性の変更 </h1>
<h1 className="title">{getMessage('contextmenu.line.property.edit')} </h1>
<button className="modal-close" onClick={() => closePopup(id)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="guide">
<span className="mb10">属性を変更する辺を選択してください</span>
<span>選択した値 [龍丸]</span>
<span className="mb10">{getMessage('modal.line.property.edit.info')}</span>
<span>
{getMessage('modal.line.property.edit.selected')} [ {selectedProperty?.name} ]
</span>
</div>
<div className="properties-setting-wrap outer">
<div className="setting-tit">設定</div>
<div className="setting-tit">{getMessage('setting')}</div>
<div className="outline-wrap">
<div className="radio-grid-wrap">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01">軒先</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra02" />
<label htmlFor="ra02">ケラバ</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra03" />
<label htmlFor="ra03">龍丸</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra04" />
<label htmlFor="ra04">ケラバ左</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra05" />
<label htmlFor="ra05">ヨセムネ</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra06" />
<label htmlFor="ra06">ケラバ右</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra07" />
<label htmlFor="ra07">片側の流れ</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra08" />
<label htmlFor="ra08"></label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra09" />
<label htmlFor="ra09">壁取り</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra10" />
<label htmlFor="ra10">Lの捨て渓谷</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra11" />
<label htmlFor="ra11">壁取り()</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra12" />
<label htmlFor="ra12">マンサード</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra13" />
<label htmlFor="ra13">壁取合(流れ)</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra14" />
<label htmlFor="ra14">設定なし</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra15" />
<label htmlFor="ra15">壁取合(流れ左)</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra16" />
<label htmlFor="ra16">壁取り(流れ右)</label>
</div>
{properties.map((property, index) => {
return (
<div className="d-check-radio pop" key={index}>
<input
type="radio"
name="radio01"
id={'ra' + (index + 1 >= 10 ? index + 1 : `0${index + 1}`)}
onChange={(e) => setSelectedProperty(property)}
/>
<label htmlFor={'ra' + (index + 1 > 10 ? index + 1 : `0${index + 1}`)}>{property.name}</label>
</div>
)
})}
</div>
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">保存</button>
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
</div>
</div>
</div>

View File

@ -13,26 +13,26 @@ export default function DormerOffset(props) {
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap xm`}>
<div className="modal-head">
<h1 className="title">도머 오프셋 </h1>
<h1 className="title">{getMessage('contextmenu.dormer.offset')}</h1>
<button className="modal-close" onClick={() => closePopup(id)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="grid-option-tit">移動する方向を入力してください</div>
<div className="grid-option-tit">{getMessage('modal.dormer.offset.info')}</div>
<div className="grid-option-wrap">
<div className="grid-option-box">
<div className="move-form">
<p className="mb5">長さ</p>
<p className="mb5">{getMessage('length')}</p>
<div className="input-move-wrap mb5">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
<input type="text" className="input-origin" defaultValue={0} />
</div>
<span>mm</span>
</div>
<div className="input-move-wrap">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
<input type="text" className="input-origin" defaultValue={0} />
</div>
<span>mm</span>
</div>
@ -46,7 +46,7 @@ export default function DormerOffset(props) {
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">保存</button>
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
</div>
</div>
</div>

View File

@ -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 (
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap ssm`}>
<div className="modal-head">
<h1 className="title">サイズ変更 </h1>
<h1 className="title">{getMessage('modal.size.setting')} </h1>
<button className="modal-close" onClick={() => closePopup(id)}>
닫기
</button>
@ -30,11 +28,11 @@ export default function SizeSetting(props) {
<div className="size-option-top">
<div className="size-option-wrap">
<div className="size-option mb5">
<input type="text" className="input-origin mr5" defaultValue={1000} readOnly />
<input type="text" className="input-origin mr5" defaultValue={1000} readOnly value={target?.width * 10 * 2} />
<span className="normal-font">mm</span>
</div>
<div className="size-option">
<input type="text" className="input-origin mr5" defaultValue={1000} />
<input type="text" className="input-origin mr5" defaultValue={1000} value={target?.width * 10 * 2} />
<span className="normal-font">mm</span>
</div>
</div>
@ -43,11 +41,11 @@ export default function SizeSetting(props) {
<div className="size-option-side">
<div className="size-option-wrap">
<div className="size-option mb5">
<input type="text" className="input-origin mr5" defaultValue={1000} readOnly />
<input type="text" className="input-origin mr5" defaultValue={1000} readOnly value={target?.height * 10} />
<span className="normal-font">mm</span>
</div>
<div className="size-option">
<input type="text" className="input-origin mr5" defaultValue={1000} />
<input type="text" className="input-origin mr5" defaultValue={1000} value={target?.height * 10} />
<span className="normal-font">mm</span>
</div>
</div>
@ -62,7 +60,7 @@ export default function SizeSetting(props) {
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">作成</button>
<button className="btn-frame modal act">{getMessage('write')}</button>
</div>
</div>
</div>

View File

@ -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 (
<WithDraggable isShow={true} handle=".penal-wrap" pos={pos}>
<div className={`penal-wrap ${!isFold ? 'act' : ''}`}>
<h2>{getMessage('modal.panel.batch.statistic')}</h2>
<button className="penal-arr" onClick={() => setIsFold(!isFold)}></button>
<div className="penal-table-wrap">
<table className="penal-table">
<thead>
<tr>
<th>{getMessage('modal.panel.batch.statistic.roof.shape')}</th>
<th>{getMessage('modal.panel.batch.statistic.power.generation.amount')} (kW)</th>
</tr>
</thead>
<tbody>
<tr>
<td>{getMessage('modal.panel.batch.statistic.total')}</td>
<td className="al-r">0.000</td>
</tr>
</tbody>
</table>
</div>
</div>
</WithDraggable>
)
}

View File

@ -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()

View File

@ -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 = {

View File

@ -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 (

View File

@ -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 (
<>
<div className="outline-form mb10">
@ -9,9 +12,9 @@ export default function Eaves({ offsetRef, pitchRef }) {
{getMessage('slope')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={4} ref={pitchRef} />
<input type="text" className="input-origin block" defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8} ref={pitchRef} />
</div>
<span className="thin"></span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form mb10">
<span className="mr10" style={{ width: '63px' }}>

View File

@ -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 (
<>
<div className="outline-form mb10">
@ -9,9 +12,9 @@ export default function Gable({ offsetRef, pitchRef }) {
{getMessage('slope')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={4} ref={pitchRef} />
<input type="text" className="input-origin block" defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8} ref={pitchRef} />
</div>
<span className="thin"></span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form mb10">
<span className="mr10" style={{ width: '63px' }}>

View File

@ -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 (
<div className="setting-box">
@ -12,7 +12,7 @@ export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
</div>
<span className="thin">{getMessage('size')}</span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form mb10">
<span className="mr10" style={{ width: '60px' }}>

View File

@ -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 (
<div className="setting-box">
<div className="outline-form mb10">
@ -13,7 +13,7 @@ export default function Pattern(props) {
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
</div>
<span className="thin"> {getMessage('size')}</span>
<span className="thin"> {pitchText}</span>
</div>
<div className="outline-form mb10">
<span className="mr10" style={{ width: '60px' }}>

View File

@ -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 (
<div className="setting-box">
@ -15,7 +15,7 @@ export default function Ridge(props) {
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
</div>
<span className="thin">{getMessage('size')}</span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form">
<span className="mr10" style={{ width: '24px' }}>

View File

@ -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()

View File

@ -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 })
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
</div>
<span className="thin">{getMessage('size')}</span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form">
<span className="mr10" style={{ width: '24px' }}>

View File

@ -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
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
</div>
<span className="thin">{getMessage('size')}</span>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form mb10">
<span className="mr10" style={{ width: '60px' }}>

View File

@ -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)}
/>
</div>
<span className="thin">{getMessage('size')}</span>
<span className="thin">{pitchText}</span>
</div>
</>
)

View File

@ -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')
}
})
}

View File

@ -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() {
<div className="main-product-list-wrap">
<div className="main-product-list">
<ProductItem num={1} name={getMessage('main.content.objectList')}>
<ul className="recently-list">
{objectList.map((row) => {
return (
<li
key={row.objectNo}
className="recently-item"
onClick={() => {
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()}`)
}
}}
>
<div className="item-inner">
<span className="time">{dayjs(row.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}</span>
<span>{row.objectNo}</span>
<span>{row.objectName}</span>
<span>{row.saleStoreName}</span>
</div>
</li>
)
})}
</ul>
{objectList.length > 0 ? (
<ul className="recently-list">
{objectList.map((row) => {
return (
<li
key={row.objectNo}
className="recently-item"
onClick={() => {
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()}`)
}
}}
>
<div className="item-inner">
<span className="time">{dayjs(row.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}</span>
<span>{row.objectNo}</span>
<span>{row.objectName}</span>
<span>{row.saleStoreName}</span>
</div>
</li>
)
})}
</ul>
) : (
<MainSkeleton count={6} />
)}
</ProductItem>
<ProductItem num={2} name={getMessage('main.content.notice')}>
<div className="notice-box">
@ -142,7 +147,9 @@ export default function MainContents() {
<div className="notice-title">{recentNoticeList[0]?.title}</div>
<div className="notice-contents">{recentNoticeList[0]?.contents}</div>
</>
) : null}
) : (
<MainSkeleton count={5} />
)}
</div>
</ProductItem>
</div>
@ -163,7 +170,9 @@ export default function MainContents() {
)
})}
</>
) : null}
) : (
<MainSkeleton count={2} />
)}
</ul>
</ProductItem>
<ProductItem num={4} name={'Data Download'}>

View File

@ -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() {
<span>{convertNumberToPriceDecimal(totalCount)}</span>
</li>
<li>
{getMessage('stuff.search.grid.selected')}
<span className="red">{convertNumberToPriceDecimal(selectedRowDataCount)}</span>
{/* {getMessage('stuff.search.grid.selected')} */}
{/* <span className="red">{convertNumberToPriceDecimal(selectedRowDataCount)}</span> */}
</li>
</ul>
</div>
@ -428,7 +428,7 @@ export default function Stuff() {
</div>
<div className="grid-table-wrap">
<div className="q-grid">
<StuffQGrid {...gridProps} getSelectedRowdata={getSelectedRowdata} getCellDoubleClicked={getCellDoubleClicked} gridRef={gridRef} />
<StuffQGrid {...gridProps} getCellDoubleClicked={getCellDoubleClicked} gridRef={gridRef} />
<div className="pagination-wrap">
<QPagination pageNo={pageNo} pageSize={pageSize} pagePerBlock={10} totalCount={totalCount} handleChangePage={handleChangePage} />
</div>

View File

@ -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', //(IIIIV / )
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', 'IIIIV')
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 (
<components.NoOptionsMessage {...props}>
<span style={{ background: 'red' }} className="custom-css-class">
TEXTTTTTTT
</span>
</components.NoOptionsMessage>
)
}
return (
<>
{(editMode === 'NEW' && (
@ -1162,7 +1210,7 @@ export default function StuffDetail() {
)) ||
null}
</div>
<Button className="btn-origin grey" onClick={onSearchDesignRequestPopOpen}>
<Button className="btn-origin grey" onPress={onSearchDesignRequestPopOpen}>
{getMessage('stuff.planReqPopup.title')}
</Button>
</div>
@ -1174,7 +1222,7 @@ export default function StuffDetail() {
</th>
<td>
<div className="input-wrap" style={{ width: '500px' }}>
<input type="text" className="input-light" {...form.register('receiveUser')} />
<input type="text" className="input-light" {...form.register('receiveUser')} ref={inputReceiveUserEl} />
</div>
</td>
</tr>
@ -1203,7 +1251,7 @@ export default function StuffDetail() {
})}
{/* 라디오끝 */}
<div className="input-wrap mr5" style={{ width: '545px' }}>
<input type="text" className="input-light" {...form.register('objectName')} />
<input type="text" className="input-light" {...form.register('objectName')} ref={inputObjectNameEl} />
</div>
<div className="select-wrap" style={{ width: '120px' }}>
<Select
@ -1293,6 +1341,7 @@ export default function StuffDetail() {
<div className="select-wrap mr5" style={{ width: '567px' }}>
<Select
id="long-value-select2"
// components={{ NoOptionsMessage }}
instanceId="long-value-select2"
className="react-select-custom"
classNamePrefix="custom"
@ -1328,9 +1377,9 @@ export default function StuffDetail() {
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input type="text" className="input-light" disabled value={form.watch('zipNo') || ''} />
<input type="text" className="input-light" disabled value={form.watch('zipNo') || ''} ref={inputZipNoEl} />
</div>
<Button className="btn-origin grey" onClick={onSearchPostNumberPopOpen}>
<Button className="btn-origin grey" onPress={onSearchPostNumberPopOpen}>
{getMessage('stuff.detail.btn.addressPop')}
</Button>
<div className="guide">{getMessage('stuff.detail.btn.addressPop.guide')}</div>
@ -1344,7 +1393,7 @@ export default function StuffDetail() {
</th>
<td>
<div className="flx-box">
<div className="select-wrap" style={{ width: '200px' }}>
<div className="select-wrap mr5" style={{ width: '200px' }}>
{prefCodeList?.length > 0 && (
<Select
id="long-value-select3"
@ -1364,7 +1413,13 @@ export default function StuffDetail() {
)}
</div>
<div className="input-wrap mr5" style={{ width: '580px' }}>
<input type="text" className="input-light" value={form.watch('address') || ''} {...form.register('address')} />
<input
type="text"
className="input-light"
value={form.watch('address') || ''}
{...form.register('address')}
ref={inputAddressEl}
/>
</div>
</div>
</td>
@ -1420,7 +1475,7 @@ export default function StuffDetail() {
></Select>
</div>
<span className="mr10">{getMessage('stuff.detail.standardWindSpeedIdSpan')}</span>
<Button type="button" className="btn-origin grey" onClick={onSearchWindSpeedPopOpen}>
<Button type="button" className="btn-origin grey" onPress={onSearchWindSpeedPopOpen}>
{getMessage('stuff.detail.btn.windSpeedPop')}
</Button>
</div>
@ -1439,6 +1494,7 @@ export default function StuffDetail() {
onKeyUp={handleKeyUp}
value={form.watch('verticalSnowCover') || ''}
{...register('verticalSnowCover')}
ref={inputVerticalSnowCoverEl}
/>
</div>
<span className="mr10">cm</span>
@ -1486,6 +1542,7 @@ export default function StuffDetail() {
onKeyUp={handleKeyUp}
value={form.watch('installHeight') || ''}
{...register('installHeight')}
ref={inputInstallHeightEl}
/>
</div>
<span>m</span>
@ -1520,7 +1577,7 @@ export default function StuffDetail() {
</div>
<div className="sub-right-footer">
{!isFormValid ? (
<Button className="btn-origin grey mr5" onClick={onTempSave}>
<Button className="btn-origin grey mr5" onPress={onTempSave}>
{getMessage('stuff.detail.btn.tempSave')}
</Button>
) : (
@ -1571,7 +1628,7 @@ export default function StuffDetail() {
{/* {detailData?.tempFlg === '1' ? ( */}
{objectNo.substring(0, 1) === 'T' ? (
<>
<Button className="btn-origin grey" onClick={onSearchDesignRequestPopOpen}>
<Button className="btn-origin grey" onPress={onSearchDesignRequestPopOpen}>
{getMessage('stuff.planReqPopup.title')}
</Button>
</>
@ -1585,7 +1642,13 @@ export default function StuffDetail() {
</th>
<td>
<div className="input-wrap" style={{ width: '500px' }}>
<input type="text" className="input-light" {...form.register('receiveUser')} value={form.watch('receiveUser') || ''} />
<input
type="text"
className="input-light"
{...form.register('receiveUser')}
value={form.watch('receiveUser') || ''}
ref={inputReceiveUserEl}
/>
</div>
</td>
</tr>
@ -1614,7 +1677,7 @@ export default function StuffDetail() {
})}
{/* 상세라디오끝 */}
<div className="input-wrap mr5" style={{ width: '545px' }}>
<input type="text" className="input-light" {...form.register('objectName')} />
<input type="text" className="input-light" {...form.register('objectName')} ref={inputObjectNameEl} />
</div>
<div className="select-wrap" style={{ width: '120px' }}>
<Select
@ -1739,9 +1802,9 @@ export default function StuffDetail() {
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input type="text" className="input-light" disabled value={form.watch('zipNo') || ''} />
<input type="text" className="input-light" disabled value={form.watch('zipNo') || ''} ref={inputZipNoEl} />
</div>
<Button className="btn-origin grey" onClick={onSearchPostNumberPopOpen}>
<Button className="btn-origin grey" onPress={onSearchPostNumberPopOpen}>
{getMessage('stuff.detail.btn.addressPop')}
</Button>
<div className="guide">{getMessage('stuff.detail.btn.addressPop.guide')}</div>
@ -1756,7 +1819,7 @@ export default function StuffDetail() {
</th>
<td>
<div className="flx-box">
<div className="select-wrap" style={{ width: '200px' }}>
<div className="select-wrap mr5" style={{ width: '200px' }}>
{prefCodeList?.length > 0 && (
<Select
id="long-value-select3"
@ -1776,7 +1839,13 @@ export default function StuffDetail() {
)}
</div>
<div className="input-wrap mr5" style={{ width: '580px' }}>
<input type="text" className="input-light" value={form.watch('address') || ''} {...form.register('address')} />
<input
type="text"
className="input-light"
value={form.watch('address') || ''}
{...form.register('address')}
ref={inputAddressEl}
/>
</div>
</div>
</td>
@ -1836,7 +1905,7 @@ export default function StuffDetail() {
></Select>
</div>
<span className="mr10">{getMessage('stuff.detail.standardWindSpeedIdSpan')}</span>
<Button type="button" className="btn-origin grey" onClick={onSearchWindSpeedPopOpen}>
<Button type="button" className="btn-origin grey" onPress={onSearchWindSpeedPopOpen}>
{getMessage('stuff.detail.btn.windSpeedPop')}
</Button>
</div>
@ -1857,6 +1926,7 @@ export default function StuffDetail() {
onKeyUp={handleKeyUp}
value={form.watch('verticalSnowCover') || ''}
{...register('verticalSnowCover')}
ref={inputVerticalSnowCoverEl}
/>
</div>
<span className="mr10">cm</span>
@ -1908,6 +1978,7 @@ export default function StuffDetail() {
onKeyUp={handleKeyUp}
value={form.watch('installHeight') || ''}
{...register('installHeight')}
ref={inputInstallHeightEl}
/>
</div>
<span>m</span>
@ -1981,13 +2052,13 @@ export default function StuffDetail() {
<div className="sub-right-footer">
<Link href="/management/stuff" scroll={false}>
<button type="button" className="btn-origin grey mr5">
R상세: {getMessage('stuff.detail.btn.moveList')}
{getMessage('stuff.detail.btn.moveList')}
</button>
</Link>
<Button type="submit" className="btn-origin navy mr5">
R상세:{getMessage('stuff.detail.btn.save')}
{getMessage('stuff.detail.btn.save')}
</Button>
<Button type="button" className="btn-origin grey" onClick={onDelete}>
<Button type="button" className="btn-origin grey" onPress={onDelete}>
{getMessage('stuff.detail.btn.delete')}
</Button>
</div>
@ -1996,17 +2067,17 @@ export default function StuffDetail() {
<>
<div className="sub-right-footer">
{!isFormValid ? (
<Button className="btn-origin grey mr5" onClick={onTempSave}>
TEMP상세:{getMessage('stuff.detail.btn.tempSave')}
<Button className="btn-origin grey mr5" onPress={onTempSave}>
{getMessage('stuff.detail.btn.tempSave')}
</Button>
) : (
<Button type="submit" className="btn-origin navy mr5">
TEMP상세:{getMessage('stuff.detail.btn.save')}
{getMessage('stuff.detail.btn.save')}
</Button>
)}
<Link href="/management/stuff" scroll={false}>
<button type="button" className="btn-origin grey">
TEMP상세:{getMessage('stuff.detail.btn.moveList')}
{getMessage('stuff.detail.btn.moveList')}
</button>
</Link>
</div>

View File

@ -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() {
</div>
<div className="sub-table-box">
<div className="info-title">{getMessage('stuff.detail.header.lastEditDatetime')}</div>
<div className="info-inner">{headerData.lastEditDatetime}</div>
<div className="info-inner">
{headerData?.lastEditDatetime ? `${dayjs(headerData.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}` : ''}{' '}
{headerData?.lastEditUserName ? `(${headerData.lastEditUserName})` : null}
</div>
</div>
<div className="sub-table-box">
<div className="info-title">{getMessage('stuff.detail.header.createDatetime')}</div>
<div className="info-inner">{headerData.createDatetime}</div>
<div className="info-inner">
{headerData?.createDatetime ? `${dayjs(headerData.lastEditDatetime).format('YYYY.MM.DD')}` : ''}{' '}
{headerData?.createUserName ? `(${headerData.createUserName})` : null}
</div>
</div>
</div>
)

View File

@ -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 (
<div className="ag-theme-quartz" style={{ height: '100%' }}>
<AgGridReact
onGridReady={onGridReady}
// onGridReady={onGridReady}
rowBuffer={rowBuffer}
rowData={rowData}
columnDefs={colDefs}

View File

@ -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 StuffQGrid(props) {
const { getMessage } = useMessage()
const { gridData, gridColumns, isPageable = true, count, gridRef } = props
/**
* 데이터를 설정할 useState을 사용하여 렌더링 전반에 걸쳐 일관된 배열 참조를 유지하는 것이 좋습니다
@ -59,10 +62,10 @@ export default function StuffQGrid(props) {
[count],
)
//
const onSelectionChanged = useCallback((event) => {
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={'<span className="ag-overlay-loading-center">물건 목록이 없습니다.</span>'}
overlayNoRowsTemplate={`<span className="ag-overlay-loading-center">${getMessage('stuff.grid.noData')}</span>`}
getRowClass={getRowClass}
autoSizeAllColumns={true}
/>

View File

@ -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 (
<div className="ag-theme-quartz" style={{ height: 500 }}>
<div className="ag-theme-quartz" style={{ height: 400 }}>
<AgGridReact
onGridReady={onGridReady}
rowBuffer={rowBuffer}
@ -56,6 +59,7 @@ export default function FindAddressPopGrid(props) {
rowSelection={'singleRow'}
pagination={isPageable}
onSelectionChanged={onSelectionChanged}
overlayNoRowsTemplate={`<span className="ag-overlay-loading-center">${getMessage('stuff.grid.noData')}</span>`}
/>
</div>
)

View File

@ -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 (
<div className="modal-popup">
<div className="modal-dialog big">
@ -301,6 +307,7 @@ export default function PlanRequestPop(props) {
type="text"
className="input-light"
value={schPlanReqNo}
onKeyUp={handleKeyUp}
onChange={(e) => {
setSchPlanReqNo(e.target.value)
}}

View File

@ -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={`<span className="ag-overlay-loading-center">${getMessage('stuff.grid.noData')}</span>`}
/>
</div>
)

View File

@ -1,5 +0,0 @@
import style from '@/components/ui/Loading.module.css'
export default function Loading() {
return <span className={style.loader}></span>
}

View File

@ -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);
}
}

View File

@ -0,0 +1,13 @@
import Skeleton from 'react-loading-skeleton'
import 'react-loading-skeleton/dist/skeleton.css'
export default function MainSkeleton({ count }) {
return (
<>
<div className="mb-6">
<Skeleton height={`50px`} />
</div>
<Skeleton count={count} />
</>
)
}

View File

@ -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 }
}

151
src/hooks/common/useRoof.js Normal file
View File

@ -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 {}
}

View File

@ -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,

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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,
}
}

View File

@ -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

View File

@ -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

View File

@ -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: <GridMove id={popupId} />,
},
{
id: 'gridCopy',
name: '그리드 복사',
name: getMessage('modal.grid.copy'),
component: <GridCopy id={popupId} />,
},
{
id: 'gridColorEdit',
name: '그리드 색 변경',
name: getMessage('modal.grid.color.edit'),
component: <ColorPickerModal id={popupId} color={gridColor} setColor={setGridColor} />,
},
{
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: <AuxiliarySize id={popupId} />,
},
{
id: 'auxiliaryMove',
name: '보조선 이동(M)',
name: `${getMessage('contextmenu.auxiliary.move')}(M)`,
shortcut: ['m', 'M'],
component: <AuxiliaryMove id={popupId} />,
},
{
id: 'auxiliaryCopy',
name: '보조선 복사(C)',
name: `${getMessage('contextmenu.auxiliary.copy')}(C)`,
shortcut: ['c', 'C'],
component: <AuxiliaryCopy id={popupId} />,
},
{
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: <LinePropertySetting id={popupId} />,
},
{
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: <SizeSetting id={popupId} />,
name: getMessage('contextmenu.size.edit'),
component: <SizeSetting id={popupId} target={currentObject} />,
},
{
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: <RoofMaterialSetting id={popupId} />,
},
{
id: 'dormerOffset',
name: '도머 오프셋',
name: getMessage('contextmenu.dormer.offset'),
component: <DormerOffset id={popupId} />,
},
],
@ -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: <RoofAllocationSetting id={popupId} />,
},
{
id: 'linePropertyEdit',
name: '각 변 속성 변경',
name: getMessage('contextmenu.line.property.edit'),
component: <LinePropertySetting id={popupId} />,
},
{
id: 'flowDirectionEdit',
name: '흐름 뱡향 변경',
component: <FlowDirectionSetting id={popupId} />,
name: getMessage('contextmenu.flow.direction.edit'),
component: <FlowDirectionSetting id={popupId} target={currentObject} />,
},
],
])
@ -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: <FontSetting id={popupId} type={'commonText'} />,
},
{
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: <DimensionLineSetting id={popupId} />,
},
],
@ -418,20 +454,23 @@ export function useContextMenu({ externalFn }) {
[
{
id: 'sizeEdit',
name: '사이즈 변경',
name: getMessage('contextmenu.size.edit'),
component: <SizeSetting id={popupId} />,
},
{
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,
}
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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,

View File

@ -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": "直角",

View File

@ -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": "직각",

View File

@ -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' ? '寸' : '度'
},
})

11
src/store/contextMenu.js Normal file
View File

@ -0,0 +1,11 @@
import { atom } from 'recoil'
export const contextMenuState = atom({
key: 'contextMenuState',
default: {
visible: false,
x: 0,
y: 0,
},
dangerouslyAllowMutability: true,
})

View File

@ -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',
},
],
},
})

View File

@ -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{

View File

@ -679,7 +679,6 @@
.infomation-box-wrap{
display: flex;
align-items: center;
gap: 10px;
.sub-table-box{
flex: 1 ;

View File

@ -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;

View File

@ -252,7 +252,6 @@
.faq-item{
position: relative;
margin-bottom: 10px;
cursor: pointer;
.faq-item-inner{
display: flex;
align-items: center;

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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}

View File

@ -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, '')
}
}

View File

@ -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"