Merge branch 'dev'
This commit is contained in:
commit
f5c7c7cb1b
@ -12,6 +12,7 @@
|
||||
"@nextui-org/react": "^2.4.2",
|
||||
"ag-grid-react": "^32.0.2",
|
||||
"axios": "^1.7.3",
|
||||
"chart.js": "^4.4.6",
|
||||
"fabric": "^5.3.0",
|
||||
"framer-motion": "^11.2.13",
|
||||
"fs": "^0.0.1-security",
|
||||
@ -22,6 +23,7 @@
|
||||
"next": "14.2.3",
|
||||
"next-international": "^1.2.4",
|
||||
"react": "^18",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-datepicker": "^7.3.0",
|
||||
"react-dom": "^18",
|
||||
|
||||
@ -13,7 +13,6 @@ export async function GET(req) {
|
||||
const decodeUrl = decodeURIComponent(targetUrl)
|
||||
|
||||
const response = await fetch(decodeUrl)
|
||||
|
||||
const data = await response.arrayBuffer()
|
||||
const buffer = Buffer.from(data)
|
||||
|
||||
|
||||
9
src/app/floor-plan/simulator/[mid]/page.jsx
Normal file
9
src/app/floor-plan/simulator/[mid]/page.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
import Simulator from '@/components/simulator/Simulator'
|
||||
|
||||
export default function SimulatorPage() {
|
||||
return (
|
||||
<>
|
||||
<Simulator />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import Intro from '@/components/Intro'
|
||||
import { initCheck } from '@/util/session-util'
|
||||
|
||||
export default async function IntroPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="container mx-auto p-4 m-4 border">
|
||||
<Intro />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -159,6 +159,9 @@ export const SAVE_KEY = [
|
||||
'groupName',
|
||||
'lineDirection',
|
||||
'groupId',
|
||||
'planeSize',
|
||||
'actualSize',
|
||||
'surfaceId',
|
||||
]
|
||||
|
||||
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype]
|
||||
|
||||
@ -1,142 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { modalContent, modalState } from '@/store/modalAtom'
|
||||
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
|
||||
import { Button } from '@nextui-org/react'
|
||||
|
||||
import SingleDatePicker from './common/datepicker/SingleDatePicker'
|
||||
import RangeDatePicker from './common/datepicker/RangeDatePicker'
|
||||
import QGrid from './common/grid/QGrid'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
|
||||
export default function Intro() {
|
||||
const { get } = useAxios()
|
||||
const { swalFire } = useSwal()
|
||||
|
||||
// const [open, setOpen] = useState(false)
|
||||
const [startDate, setStartDate] = useState(new Date())
|
||||
const singleDatePickerProps = {
|
||||
startDate,
|
||||
setStartDate,
|
||||
}
|
||||
|
||||
const [dateRange, setDateRange] = useState([null, null])
|
||||
const [startRangeDate, endRangeDate] = dateRange
|
||||
const rangeDatePickerProps = {
|
||||
startRangeDate,
|
||||
endRangeDate,
|
||||
setDateRange,
|
||||
}
|
||||
|
||||
// const gridProps = {
|
||||
// isPageable: false,
|
||||
// }
|
||||
const [gridProps, setGridProps] = useState({
|
||||
gridData: [],
|
||||
isPageable: false,
|
||||
})
|
||||
|
||||
const [open, setOpen] = useRecoilState(modalState)
|
||||
const [contents, setContent] = useRecoilState(modalContent)
|
||||
|
||||
const modelProps = {
|
||||
open,
|
||||
setOpen,
|
||||
}
|
||||
|
||||
const ipsum = (
|
||||
<>
|
||||
<p className="text-2xl">title</p>
|
||||
<p>
|
||||
저작자·발명가·과학기술자와 예술가의 권리는 법률로써 보호한다. 이 헌법은 1988년 2월 25일부터 시행한다. 다만, 이 헌법을 시행하기 위하여 필요한
|
||||
법률의 제정·개정과 이 헌법에 의한 대통령 및 국회의원의 선거 기타 이 헌법시행에 관한 준비는 이 헌법시행 전에 할 수 있다.
|
||||
</p>
|
||||
<p>
|
||||
국가는 주택개발정책등을 통하여 모든 국민이 쾌적한 주거생활을 할 수 있도록 노력하여야 한다. 통신·방송의 시설기준과 신문의 기능을 보장하기
|
||||
위하여 필요한 사항은 법률로 정한다.
|
||||
</p>
|
||||
<p>
|
||||
국회에서 의결된 법률안은 정부에 이송되어 15일 이내에 대통령이 공포한다. 선거에 관한 경비는 법률이 정하는 경우를 제외하고는 정당 또는
|
||||
후보자에게 부담시킬 수 없다.
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
// const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
||||
// const data = await response.json()
|
||||
const data = await get({ url: 'https://www.ag-grid.com/example-assets/space-mission-data.json' })
|
||||
setGridProps({ ...gridProps, gridData: data })
|
||||
}
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-2xl">
|
||||
<Link href={'/login'}>
|
||||
<Button color="primary">로그인 페이지로 이동</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">Single Date Picker</div>
|
||||
<div>
|
||||
<SingleDatePicker {...singleDatePickerProps} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">Range Date Picker</div>
|
||||
<div>
|
||||
<RangeDatePicker {...rangeDatePickerProps} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">QGrid</div>
|
||||
<div>
|
||||
<QGrid {...gridProps} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">QModal</div>
|
||||
<div>
|
||||
{/* <Button color="primary" onClick={() => setOpen(true)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<QModal {...modelProps}>{ipsum}</QModal> */}
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setContent(ipsum)
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
Open Modal
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<div className="text-2xl">QToast</div>
|
||||
<div>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
swalFire({
|
||||
text: 'This is a toast message',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Open Toast
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -431,7 +431,25 @@ export default function Roof2(props) {
|
||||
{ x: 450, y: 850 },
|
||||
]
|
||||
|
||||
const polygon = new QPolygon(rectangleType2, {
|
||||
const test1 = [
|
||||
{ x: 381, y: 178 },
|
||||
{ x: 381, y: 659.3 },
|
||||
{ x: 773.3, y: 659.3 },
|
||||
{ x: 773.3, y: 497.9 },
|
||||
{ x: 1457, y: 497.9 },
|
||||
{ x: 1457, y: 178 },
|
||||
]
|
||||
|
||||
const test2 = [
|
||||
{ x: 113, y: 114.9 },
|
||||
{ x: 113, y: 371.9 },
|
||||
{ x: 762, y: 371.9 },
|
||||
{ x: 762, y: 818.7 },
|
||||
{ x: 1468.6, y: 818.7 },
|
||||
{ x: 1468.6, y: 114.9 },
|
||||
]
|
||||
|
||||
const polygon = new QPolygon(type2, {
|
||||
fill: 'transparent',
|
||||
stroke: 'green',
|
||||
strokeWidth: 1,
|
||||
|
||||
@ -3,61 +3,73 @@
|
||||
import { useEffect, useState, useContext } from 'react'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||
import { SessionContext } from '@/app/SessionProvider'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||
import SingleDatePicker from '../common/datepicker/SingleDatePicker'
|
||||
import EstimateFileUploader from './EstimateFileUploader'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { isNotEmptyArray, isObjectNotEmpty } from '@/util/common-utils'
|
||||
import { isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
import Select from 'react-select'
|
||||
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
||||
import { SessionContext } from '@/app/SessionProvider'
|
||||
import Select, { components } from 'react-select'
|
||||
import { convertNumberToPriceDecimal } from '@/util/common-utils'
|
||||
import ProductFeaturesPop from './popup/ProductFeaturesPop'
|
||||
|
||||
export default function Estimate({ params }) {
|
||||
const { session } = useContext(SessionContext)
|
||||
const [objectNo, setObjectNo] = useState('') //물건번호
|
||||
const [planNo, setPlanNo] = useState('') //플랜번호
|
||||
|
||||
const [files, setFiles] = useState([]) // 보내는 첨부파일
|
||||
const [originFiles, setOriginFiles] = useState([]) //기존 첨부파일
|
||||
|
||||
const [showContentCode, setShowContentCode] = useState('ATTR001')
|
||||
|
||||
const [productFeaturesPopupOpen, setProductFeaturesPopupOpen] = useState(false) //견적특이사항 팝업
|
||||
const [showProductFeatureData, setShowProductFeatureData] = useState([]) //팝업에 보여줄 견적특이사항 데이터
|
||||
|
||||
//견적특이사항 접고 펼치기
|
||||
const [hidden, setHidden] = useState(false)
|
||||
|
||||
//아이템 자동완성 리스트
|
||||
const [displayItemList, setDisplayItemList] = useState([])
|
||||
|
||||
//공통코드
|
||||
const { findCommonCode } = useCommonCode()
|
||||
const [honorificCodeList, setHonorificCodeList] = useState([]) //경칭 공통코드
|
||||
|
||||
const [storePriceList, setStorePriceList] = useState([]) //가격표시 option
|
||||
|
||||
const [tempPriceCd, setTempPriceCd] = useState('')
|
||||
|
||||
const [startDate, setStartDate] = useState(new Date())
|
||||
const singleDatePickerProps = {
|
||||
startDate,
|
||||
setStartDate,
|
||||
}
|
||||
|
||||
const { session } = useContext(SessionContext)
|
||||
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||
|
||||
//견적서 상세데이터
|
||||
const { state, setState } = useEstimateController(params.pid)
|
||||
|
||||
//견적특이사항 상세 데이터 LIST
|
||||
const [itemList, setItemList] = useState([]) //기존 아이템 리스트
|
||||
|
||||
//견적특이사항 List
|
||||
const [specialNoteList, setSpecialNoteList] = useState([])
|
||||
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const { get, post } = useAxios(globalLocaleState)
|
||||
const { get, promisePost } = useAxios(globalLocaleState)
|
||||
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const { setMenuNumber } = useCanvasMenu()
|
||||
|
||||
//새로 추가한 첨부파일 props
|
||||
const fileUploadProps = {
|
||||
// objectNo: '',
|
||||
// planNo: params.pid,
|
||||
// category: '10',
|
||||
uploadFiles: files,
|
||||
setUploadFiles: setFiles,
|
||||
}
|
||||
@ -72,6 +84,17 @@ export default function Estimate({ params }) {
|
||||
if (code1 != null) {
|
||||
setHonorificCodeList(code1)
|
||||
}
|
||||
|
||||
//아이템 자동완성 목록 가져오기
|
||||
const param = {
|
||||
saleStoreId: session.storeId,
|
||||
}
|
||||
const apiUrl = `/api/display-item/item-list?${queryStringFormatter(param)}`
|
||||
get({ url: apiUrl }).then((res) => {
|
||||
if (res.length > 0) {
|
||||
setDisplayItemList(res)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@ -123,24 +146,130 @@ export default function Estimate({ params }) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
// 첨부파일 state에 넣기
|
||||
// 추가한 첨부파일 state에 넣기
|
||||
useEffect(() => {
|
||||
// console.log(files)
|
||||
if (files.length > 0) {
|
||||
if (isNotEmptyArray(files)) {
|
||||
files.map((row) => {
|
||||
setState({ fileList: row.data })
|
||||
})
|
||||
} else {
|
||||
console.log('첨부파일 없음')
|
||||
setState({ fileList: [] })
|
||||
}
|
||||
}, [files])
|
||||
|
||||
//상세에서 내려온 첨부파일 set 만들기
|
||||
useEffect(() => {
|
||||
if (isNotEmptyArray(state.fileList)) {
|
||||
setOriginFiles(state.fileList)
|
||||
}
|
||||
}, [state?.fileList])
|
||||
|
||||
// 기존첨부파일 삭제
|
||||
const deleteOriginFile = async (objectNo, no) => {
|
||||
const delParams = {
|
||||
userId: session.userId,
|
||||
objectNo: objectNo,
|
||||
no: no,
|
||||
}
|
||||
|
||||
await promisePost({ url: 'api/file/fileDelete', data: delParams }).then((res) => {
|
||||
if (res.status === 204) {
|
||||
setOriginFiles(originFiles.filter((file) => file.objectNo === objectNo && file.no !== no))
|
||||
setState({
|
||||
fileList: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//아이템 목록
|
||||
useEffect(() => {
|
||||
if (isNotEmptyArray(state.itemList)) {
|
||||
setItemList(state.itemList)
|
||||
}
|
||||
}, [state?.itemList])
|
||||
|
||||
//가격표시 option 최초세팅
|
||||
useEffect(() => {
|
||||
const param = {
|
||||
saleStoreId: session.storeId,
|
||||
sapSalesStoreCd: session.custCd,
|
||||
docTpCd: state?.estimateType,
|
||||
}
|
||||
|
||||
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
|
||||
get({ url: apiUrl }).then((res) => {
|
||||
if (isNotEmptyArray(res?.data)) {
|
||||
setStorePriceList(res.data)
|
||||
}
|
||||
})
|
||||
}, [state?.estimateType])
|
||||
|
||||
useEffect(() => {
|
||||
if (state.priceCd) {
|
||||
setTempPriceCd(state.priceCd)
|
||||
}
|
||||
}, [state?.priceCd])
|
||||
|
||||
//가격표시 option 변경시
|
||||
useEffect(() => {
|
||||
if (tempPriceCd !== '') {
|
||||
const param = {
|
||||
saleStoreId: session.storeId,
|
||||
sapSalesStoreCd: session.custCd,
|
||||
docTpCd: tempPriceCd,
|
||||
}
|
||||
|
||||
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
|
||||
get({ url: apiUrl }).then((res) => {
|
||||
if (isNotEmptyArray(res?.data)) {
|
||||
setStorePriceList(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [tempPriceCd])
|
||||
|
||||
//Pricing 버튼
|
||||
const handlePricing = async (priceCd) => {
|
||||
const param = {
|
||||
saleStoreId: session.storeId,
|
||||
sapSalesStoreCd: session.custCd,
|
||||
docTpCd: state.estimateType,
|
||||
priceCd: session.storeLvl === '1' ? tempPriceCd : priceCd,
|
||||
itemIdList: state.itemList, //아이템 최초정보로 호출
|
||||
}
|
||||
|
||||
// console.log('프라이싱파람::', param)
|
||||
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
|
||||
if (res) {
|
||||
if (res.status === 200) {
|
||||
const data = res.data
|
||||
if (data.result.code === 200) {
|
||||
if (isNotEmptyArray(data.data2)) {
|
||||
//아이템쪽 다 새로고침............
|
||||
//성공후..
|
||||
//기존itemList랑 프라이싱결과랑 비교해서 단가만 업뎃 서로 갯수가 안맞을 수 있음 없는 itemId면 unitPrice 0으로
|
||||
//itemId로 비교해서 단가정보만 업데이트
|
||||
setState({
|
||||
priceCd: session.storeLvl === '1' ? tempPriceCd : priceCd,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 제품 추가 테스트
|
||||
const addItemTest = () => {
|
||||
const newItemDispOrder = (Math.max(...state.itemList.map((item) => item.dispOrder)) + 1) * 100
|
||||
console.log('newItemDispOrder::', newItemDispOrder)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sub-content estimate">
|
||||
<div className="sub-content-inner">
|
||||
{/* 물건번호, 견적서번호, 등록일, 변경일시 시작 */}
|
||||
{/* <form onSubmit={handleSubmit(onValid)}> */}
|
||||
<div className="sub-content-box">
|
||||
<div className="sub-table-box">
|
||||
<div className="estimate-list-wrap one">
|
||||
@ -188,7 +317,7 @@ export default function Estimate({ params }) {
|
||||
<tr>
|
||||
{/* 1차 판매점명 */}
|
||||
<th>{getMessage('estimate.detail.saleStoreId')}</th>
|
||||
<td></td>
|
||||
<td>{state?.firstSaleStoreName}</td>
|
||||
{/* 견적일 */}
|
||||
<th>
|
||||
{getMessage('estimate.detail.estimateDate')} <span className="important">*</span>
|
||||
@ -202,7 +331,7 @@ export default function Estimate({ params }) {
|
||||
<tr>
|
||||
{/* 2차 판매점명 */}
|
||||
<th>{getMessage('estimate.detail.otherSaleStoreId')}</th>
|
||||
<td></td>
|
||||
<td>{state?.agencySaleStoreName}</td>
|
||||
{/* 담당자 */}
|
||||
<th>
|
||||
{getMessage('estimate.detail.receiveUser')} <span className="important">*</span>
|
||||
@ -286,6 +415,7 @@ export default function Estimate({ params }) {
|
||||
value={'YJSS'}
|
||||
checked={state?.estimateType === 'YJSS' ? true : false}
|
||||
onChange={(e) => {
|
||||
//주문분류
|
||||
setState({ estimateType: e.target.value })
|
||||
}}
|
||||
/>
|
||||
@ -362,9 +492,18 @@ export default function Estimate({ params }) {
|
||||
<div className="title-wrap">
|
||||
<h3>{getMessage('estimate.detail.header.fileList1')}</h3>
|
||||
<div className="d-check-box light mr5">
|
||||
<input type="checkbox" id="next" />
|
||||
<input
|
||||
type="checkbox"
|
||||
id="next"
|
||||
checked={state?.fileFlg === '0' ? false : true}
|
||||
onChange={(e) => {
|
||||
setState({
|
||||
fileFlg: e.target.checked ? '1' : '0',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="next" style={{ color: '#101010' }}>
|
||||
{getMessage('estimate.detail.nextSubmit')}
|
||||
{getMessage('estimate.detail.fileFlg')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -395,6 +534,23 @@ export default function Estimate({ params }) {
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{getMessage('estimate.detail.header.fileList2')}</th>
|
||||
<td>
|
||||
<div className="drag-file-box">
|
||||
<ul className="file-list">
|
||||
{originFiles.length > 0 &&
|
||||
originFiles.map((originFile) => {
|
||||
return (
|
||||
<li className="file-item">
|
||||
<span>
|
||||
{originFile.faileName}
|
||||
<button className="delete" onClick={() => deleteOriginFile(originFile.objectNo, originFile.no)}></button>
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -417,47 +573,48 @@ export default function Estimate({ params }) {
|
||||
<div className="estimate-check-inner">
|
||||
<div className="special-note-check-wrap">
|
||||
{/* SpecialNoteList반복문 */}
|
||||
{specialNoteList.map((row) => {
|
||||
return (
|
||||
<div
|
||||
className="special-note-check-item"
|
||||
onClick={(event) => {
|
||||
settingShowContent(row.code, event)
|
||||
}}
|
||||
>
|
||||
<div className="d-check-box light">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={row.code}
|
||||
checked={!!row.text}
|
||||
disabled={row.code === 'ATTR001' ? true : false}
|
||||
onChange={(event) => {
|
||||
setSpecialNoteList((specialNote) =>
|
||||
specialNote.map((temp) => (temp.code === row.code ? { ...temp, text: !temp.text } : temp)),
|
||||
)
|
||||
settingShowContent(row.code, event)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={row.code}>{row.codeNm}</label>
|
||||
{specialNoteList.length > 0 &&
|
||||
specialNoteList.map((row) => {
|
||||
return (
|
||||
<div
|
||||
className="special-note-check-item"
|
||||
onClick={(event) => {
|
||||
settingShowContent(row.code, event)
|
||||
}}
|
||||
>
|
||||
<div className="d-check-box light">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={row.code}
|
||||
checked={!!row.text}
|
||||
disabled={row.code === 'ATTR001' ? true : false}
|
||||
onChange={(event) => {
|
||||
setSpecialNoteList((specialNote) =>
|
||||
specialNote.map((temp) => (temp.code === row.code ? { ...temp, text: !temp.text } : temp)),
|
||||
)
|
||||
settingShowContent(row.code, event)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={row.code}>{row.codeNm}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{/* 견적특이사항 선택한 내용?영역시작 */}
|
||||
{/* 견적특이사항 선택한 내용 영역시작 */}
|
||||
<div className="calculation-estimate">
|
||||
{specialNoteList.map((row) => {
|
||||
if (row.code === showContentCode) {
|
||||
return (
|
||||
<dl>
|
||||
<dt>{row.codeNm}</dt>
|
||||
<dd>{row.remarks}</dd>
|
||||
<dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd>
|
||||
</dl>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
{/* 견적특이사항 선택한 내용?영역끝 */}
|
||||
{/* 견적특이사항 선택한 내용 영역끝 */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -530,12 +687,29 @@ export default function Estimate({ params }) {
|
||||
<div className="product-price-wrap">
|
||||
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
|
||||
<div className="select-wrap">
|
||||
<select className="select-light" name="" id="">
|
||||
<option value="">111</option>
|
||||
<option value="">222</option>
|
||||
</select>
|
||||
{session?.storeLvl === '1' ? (
|
||||
<select
|
||||
className="select-light"
|
||||
onChange={(e) => {
|
||||
setTempPriceCd(e.target.value)
|
||||
}}
|
||||
>
|
||||
{storePriceList.length > 0 && storePriceList.map((row) => <option value={row.priceCd}>{row.priceNm}</option>)}
|
||||
</select>
|
||||
) : (
|
||||
<select className="select-light">
|
||||
<option value="UNIT_PRICE">{getMessage('estimate.detail.header.unitPrice')}</option>
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
<button className="btn-origin grey ml5">{getMessage('estimate.detail.showPrice.btn1')}</button>
|
||||
<button
|
||||
className="btn-origin grey ml5"
|
||||
onClick={() => {
|
||||
handlePricing(state.priceCd)
|
||||
}}
|
||||
>
|
||||
{getMessage('estimate.detail.showPrice.pricingBtn')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="product-edit-wrap">
|
||||
<ul className="product-edit-explane">
|
||||
@ -557,26 +731,140 @@ export default function Estimate({ params }) {
|
||||
</li>
|
||||
</ul>
|
||||
<div className="product-edit-btn">
|
||||
<button className="btn-origin navy mr5" type="submit">
|
||||
<button className="btn-origin navy mr5" type="button" onClick={() => addItemTest()}>
|
||||
<span className="plus"></span>
|
||||
{getMessage('estimate.detail.showPrice.btn2')}
|
||||
{getMessage('estimate.detail.showPrice.addItem')}
|
||||
</button>
|
||||
<button className="btn-origin grey">
|
||||
<span className="minus"></span>
|
||||
{getMessage('estimate.detail.showPrice.btn3')}
|
||||
{getMessage('estimate.detail.showPrice.delItem')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 가격표시영역끝 */}
|
||||
{/* html테이블시작 */}
|
||||
<div className="q-grid no-cols"></div>
|
||||
<div className="esimate-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={50} />
|
||||
<col width={100} />
|
||||
<col />
|
||||
<col width={200} />
|
||||
<col width={100} />
|
||||
<col width={100} />
|
||||
<col width={200} />
|
||||
<col width={240} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div className="d-check-box pop no-text" style={{ display: 'none' }}>
|
||||
<input type="checkbox" id="ch97" />
|
||||
<label htmlFor="ch97"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.dispOrder')}</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.itemId')}</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.itemNo')}</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.amount')}</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.unit')}</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.salePrice')}</th>
|
||||
<th>{getMessage('estimate.detail.itemTableHeader.saleTotPrice')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{itemList.length > 0 &&
|
||||
itemList.map((item, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="al-c">
|
||||
<div className="d-check-box light no-text">
|
||||
<input type="checkbox" id={item?.dispOrder * 100} />
|
||||
<label htmlFor={item?.dispOrder * 100}></label>
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">{item?.dispOrder * 100}</td>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="select-wrap mr5">
|
||||
<Select
|
||||
id="long-value-select1"
|
||||
instanceId="long-value-select1"
|
||||
className="react-select-custom"
|
||||
classNamePrefix="custom"
|
||||
placeholder="Select"
|
||||
options={displayItemList}
|
||||
getOptionLabel={(x) => x.itemName}
|
||||
getOptionValue={(x) => x.itemId}
|
||||
isClearable={true}
|
||||
isDisabled={false}
|
||||
value={displayItemList.filter(function (option) {
|
||||
return option.itemId === item.itemId
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{item?.itemChangeFlg === '1' && (
|
||||
<div className="btn-area">
|
||||
<span className="tb_ico change_check"></span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="name">{item?.itemNo}</div>
|
||||
<div className="icon-wrap">
|
||||
{item?.fileUploadFlg === '1' && <span className="tb_ico file_check"></span>}
|
||||
{item?.specialNoteCd && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid-tip"
|
||||
onClick={() => {
|
||||
setProductFeaturesPopupOpen(true)
|
||||
setShowProductFeatureData(item?.specialNoteCd)
|
||||
}}
|
||||
></button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="input-wrap" style={{ width: '100%' }}>
|
||||
<input type="text" className="input-light al-r" defaultValue={item?.amount} />
|
||||
</div>
|
||||
</td>
|
||||
<td>{item.unit}</td>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="input-wrap mr5">
|
||||
<input type="text" className="input-light al-r" defaultValue={convertNumberToPriceDecimal(item?.salePrice)} />
|
||||
</div>
|
||||
{/* <div className="btn-area">
|
||||
<span className="tb_ico open_check">OPEN아이콘 처리</span>
|
||||
</div> */}
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">{convertNumberToPriceDecimal(item?.saleTotPrice)}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* html테이블끝 */}
|
||||
</div>
|
||||
</div>
|
||||
{/* 기본정보끝 */}
|
||||
{/* </form> */}
|
||||
</div>
|
||||
{productFeaturesPopupOpen && (
|
||||
<ProductFeaturesPop
|
||||
specialNoteList={specialNoteList}
|
||||
showProductFeatureData={showProductFeatureData}
|
||||
setProductFeaturesPopupOpen={setProductFeaturesPopupOpen}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ export default function EstimateFileUploader({ uploadFiles, setUploadFiles }) {
|
||||
// if (res.data > 0) setUploadFiles([...files, { name: e.target.files[0].name, id: uuidv4() }])
|
||||
// })
|
||||
setUploadFiles([...uploadFiles, { data: e.target.files[0], id: uuidv4() }])
|
||||
e.target.value = ''
|
||||
}
|
||||
|
||||
const deleteFile = (id) => {
|
||||
|
||||
255
src/components/estimate/popup/DocDownOptionPop.jsx
Normal file
255
src/components/estimate/popup/DocDownOptionPop.jsx
Normal file
@ -0,0 +1,255 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||
|
||||
export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) {
|
||||
// console.log('플랜번호::::::::::::', planNo)
|
||||
const { getMessage } = useMessage()
|
||||
const { promiseGet } = useAxios()
|
||||
|
||||
//다운로드 파일 EXCEL
|
||||
const [schUnitPriceFlg, setSchUnitPriceFlg] = useState('0')
|
||||
|
||||
//견적제출서 표시명
|
||||
const [schDisplayFlg, setSchSchDisplayFlg] = useState('0')
|
||||
//가대 중량표 포함
|
||||
const [schWeightFlg, setSchWeightFlg] = useState('0')
|
||||
//도면/시뮬레이션 파일 포함
|
||||
const [schDrawingFlg, setSchDrawingFlg] = useState('0')
|
||||
|
||||
// recoil 물건번호
|
||||
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||
|
||||
//문서 다운로드
|
||||
const handleFileDown = async () => {
|
||||
// console.log('물건번호:::', objectRecoil.floorPlanObjectNo)
|
||||
// console.log('planNo::', planNo)
|
||||
// 고른 옵션값들
|
||||
//0 : 견적가 Excel 1 : 정가용Excel 2: 견적가 PDF 3 :정가용PDF
|
||||
// console.log(schUnitPriceFlg)
|
||||
// console.log(schDisplayFlg)
|
||||
// console.log(schWeightFlg)
|
||||
// console.log(schDrawingFlg)
|
||||
const url = '/api/estimate/excel-download'
|
||||
const params = {}
|
||||
const options = { responseType: 'blob' }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modal-popup">
|
||||
<div className="modal-dialog middle">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="title">{getMessage('estimate.detail.docPopup.title')}</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close"
|
||||
onClick={() => {
|
||||
setEstimatePopupOpen(false)
|
||||
}}
|
||||
>
|
||||
{getMessage('estimate.detail.docPopup.close')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="modal-body-inner">
|
||||
<div className="explane">{getMessage('estimate.detail.docPopup.explane')}</div>
|
||||
<div className="common-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style={{ width: '260px' }} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
{getMessage('estimate.detail.docPopup.schUnitPriceFlg')}
|
||||
<span className="red">*</span>
|
||||
</th>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
id="schUnitPriceFlg0"
|
||||
name="schUnitPriceFlg"
|
||||
value={'0'}
|
||||
checked={schUnitPriceFlg === '0'}
|
||||
onChange={(e) => {
|
||||
setSchUnitPriceFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schUnitPriceFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
id="schUnitPriceFlg1"
|
||||
name="schUnitPriceFlg"
|
||||
value={'1'}
|
||||
checked={schUnitPriceFlg === '1'}
|
||||
onChange={(e) => {
|
||||
setSchUnitPriceFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schUnitPriceFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
id="schUnitPricePdfFlg0"
|
||||
name="schUnitPriceFlg"
|
||||
value={'2'}
|
||||
checked={schUnitPriceFlg === '2'}
|
||||
onChange={(e) => {
|
||||
setSchUnitPriceFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schUnitPricePdfFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light ">
|
||||
<input
|
||||
type="radio"
|
||||
id="schUnitPricePdfFlg1"
|
||||
name="schUnitPriceFlg"
|
||||
value={'3'}
|
||||
checked={schUnitPriceFlg === '3'}
|
||||
onChange={(e) => {
|
||||
setSchUnitPriceFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schUnitPricePdfFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
{getMessage('estimate.detail.docPopup.schDisplayFlg')} <span className="red">*</span>
|
||||
</th>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
name="schDisplayFlg"
|
||||
id="schDisplayFlg0"
|
||||
value={'0'}
|
||||
checked={schDisplayFlg === '0'}
|
||||
onChange={(e) => {
|
||||
setSchSchDisplayFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schDisplayFlg0">{getMessage('estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light">
|
||||
<input
|
||||
type="radio"
|
||||
name="schDisplayFlg"
|
||||
id="schDisplayFlg1"
|
||||
value={'1'}
|
||||
checked={schDisplayFlg === '1'}
|
||||
onChange={(e) => {
|
||||
setSchSchDisplayFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schDisplayFlg1">{getMessage('estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
{getMessage('estimate.detail.docPopup.schWeightFlg')} <span className="red">*</span>
|
||||
</th>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
name="schWeightFlg"
|
||||
id="schWeightFlg0"
|
||||
value={'0'}
|
||||
checked={schWeightFlg === '0'}
|
||||
onChange={(e) => {
|
||||
setSchWeightFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schWeightFlg0">{getMessage('estimate.detail.docPopup.schWeightFlg.schWeightFlg0')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light">
|
||||
<input
|
||||
type="radio"
|
||||
name="schWeightFlg"
|
||||
id="schWeightFlg1"
|
||||
value={'1'}
|
||||
checked={schWeightFlg === '1'}
|
||||
onChange={(e) => {
|
||||
setSchWeightFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schWeightFlg1">{getMessage('estimate.detail.docPopup.schWeightFlg.schWeightFlg1')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{getMessage('estimate.detail.docPopup.schDrawingFlg')}</th>
|
||||
<td>
|
||||
<div className="form-flex-wrap">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
name="schDrawingFlg"
|
||||
id="schDrawingFlg0"
|
||||
value={'0'}
|
||||
checked={schDrawingFlg === '0'}
|
||||
onChange={(e) => {
|
||||
setSchDrawingFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schDrawingFlg0">{getMessage('estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light">
|
||||
<input
|
||||
type="radio"
|
||||
name="schDrawingFlg"
|
||||
id="schDrawingFlg01"
|
||||
value={'1'}
|
||||
checked={schDrawingFlg === '1'}
|
||||
onChange={(e) => {
|
||||
setSchDrawingFlg(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="schDrawingFlg01">{getMessage('estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-btn-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-origin grey mr5"
|
||||
onClick={() => {
|
||||
setEstimatePopupOpen(false)
|
||||
}}
|
||||
>
|
||||
{getMessage('estimate.detail.docPopup.close')}
|
||||
</button>
|
||||
<button type="button" className="btn-origin navy" onClick={() => handleFileDown()}>
|
||||
{getMessage('estimate.detail.docPopup.docDownload')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
67
src/components/estimate/popup/ProductFeaturesPop.jsx
Normal file
67
src/components/estimate/popup/ProductFeaturesPop.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
'use client'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
export default function ProductFeaturesPop({ specialNoteList, showProductFeatureData, setProductFeaturesPopupOpen }) {
|
||||
const [showSpecialNoteList, setShowSpecialNoteList] = useState([])
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
useEffect(() => {
|
||||
let pushData = []
|
||||
specialNoteList.map((row) => {
|
||||
let option = showProductFeatureData.split('、')
|
||||
option.map((row2) => {
|
||||
if (row.code === row2) {
|
||||
pushData.push(row)
|
||||
}
|
||||
})
|
||||
})
|
||||
setShowSpecialNoteList(pushData)
|
||||
}, [specialNoteList])
|
||||
|
||||
return (
|
||||
<div className="modal-popup">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="title">{getMessage('estimate.detail.productFeaturesPopup.title')}</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close"
|
||||
onClick={() => {
|
||||
setProductFeaturesPopupOpen(false)
|
||||
}}
|
||||
>
|
||||
{getMessage('estimate.detail.productFeaturesPopup.close')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="modal-body-inner border">
|
||||
<div className="calculation-estimate usemodal">
|
||||
{showSpecialNoteList.length > 0 &&
|
||||
showSpecialNoteList.map((row) => {
|
||||
return (
|
||||
<dl>
|
||||
<dt>{row.codeNm}</dt>
|
||||
<dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd>
|
||||
</dl>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-btn-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-origin navy"
|
||||
onClick={() => {
|
||||
setProductFeaturesPopupOpen(false)
|
||||
}}
|
||||
>
|
||||
{getMessage('estimate.detail.productFeaturesPopup.close')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,8 +1,15 @@
|
||||
import { fabric } from 'fabric'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
||||
import { calculateAngle, drawRidgeRoof, drawHippedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils'
|
||||
import {
|
||||
distanceBetweenPoints,
|
||||
findTopTwoIndexesByDistance,
|
||||
getAllRelatedObjects,
|
||||
getDirectionByPoint,
|
||||
sortedPointLessEightPoint,
|
||||
sortedPoints,
|
||||
} from '@/util/canvas-util'
|
||||
import { calculateAngle, drawRidgeRoof, drawShedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils'
|
||||
import * as turf from '@turf/turf'
|
||||
import { LINE_TYPE } from '@/common/common'
|
||||
|
||||
@ -131,12 +138,14 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
})
|
||||
|
||||
this.on('removed', () => {
|
||||
const children = this.canvas.getObjects().filter((obj) => obj.parentId === this.id)
|
||||
// const children = getAllRelatedObjects(this.id, this.canvas)
|
||||
const children = this.canvas.getObjects().filter((obj) => obj.parentId === this.id && obj.name === 'lengthText')
|
||||
children.forEach((child) => {
|
||||
this.canvas.remove(child)
|
||||
})
|
||||
})
|
||||
|
||||
//QPolygon 좌표 이동시 좌표 재계산
|
||||
this.on('polygonMoved', () => {
|
||||
//폴리곤일때만 사용
|
||||
let matrix = this.calcTransformMatrix()
|
||||
@ -148,8 +157,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
.map((p) => {
|
||||
return fabric.util.transformPoint(p, matrix)
|
||||
})
|
||||
this.set('points', transformedPoints)
|
||||
this.set('pathOffset', { x: this.left, y: this.top })
|
||||
this.points = transformedPoints
|
||||
const { left, top } = this.calcOriginCoords()
|
||||
this.set('pathOffset', { x: left, y: top })
|
||||
this.setCoords()
|
||||
})
|
||||
|
||||
@ -169,6 +179,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
offset: 0,
|
||||
},
|
||||
parent: this,
|
||||
parentId: this.id,
|
||||
direction: getDirectionByPoint(point, nextPoint),
|
||||
idx: i + 1,
|
||||
})
|
||||
@ -187,7 +198,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
||||
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
||||
|
||||
const isEaves = types.every((type) => eavesType.includes(type))
|
||||
// const isEaves = types.every((type) => eavesType.includes(type))
|
||||
const gableOdd = types.filter((type, i) => i % 2 === 0)
|
||||
const gableEven = types.filter((type, i) => i % 2 === 1)
|
||||
const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED)
|
||||
@ -199,18 +210,40 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
) {
|
||||
console.log('박공 지붕')
|
||||
} else if (hasShed) {
|
||||
//편류지붕
|
||||
let shedIndex = 0
|
||||
types.forEach((type, i) => {
|
||||
if (type === LINE_TYPE.WALLLINE.SHED) {
|
||||
shedIndex = i
|
||||
}
|
||||
const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
|
||||
const areLinesParallel = function (line1, line2) {
|
||||
const angle1 = calculateAngle(line1.startPoint, line1.endPoint)
|
||||
const angle2 = calculateAngle(line2.startPoint, line2.endPoint)
|
||||
return angle1 === angle2
|
||||
}
|
||||
|
||||
let isShedRoof = true
|
||||
sheds.forEach((shed, i) => {
|
||||
isShedRoof = areLinesParallel(shed, sheds[(i + 1) % sheds.length])
|
||||
})
|
||||
const shedOdd = types.filter((type, i) => i % 2 === shedIndex % 2).filter((type) => type !== LINE_TYPE.WALLLINE.SHED)
|
||||
const shedEven = types.filter((type, i) => i % 2 !== shedIndex % 2)
|
||||
types.forEach((type, i) => console.log(type, i, i % 2, shedIndex % 2, i % 2 === shedIndex % 2))
|
||||
if (shedOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && shedEven.every((type) => type === LINE_TYPE.WALLLINE.GABLE)) {
|
||||
console.log('편류지붕')
|
||||
if (isShedRoof) {
|
||||
const eaves = this.lines
|
||||
.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
||||
.filter((line) => {
|
||||
const angle1 = calculateAngle(sheds[0].startPoint, sheds[0].endPoint)
|
||||
const angle2 = calculateAngle(line.startPoint, line.endPoint)
|
||||
if (Math.abs(angle1 - angle2) === 180) {
|
||||
return line
|
||||
}
|
||||
})
|
||||
if (eaves.length > 0) {
|
||||
const gables = this.lines.filter((line) => sheds.includes(line) === false && eaves.includes(line) === false)
|
||||
const isGable = gables.every((line) => gableType.includes(line.attributes.type))
|
||||
if (isGable) {
|
||||
drawShedRoof(this.id, this.canvas)
|
||||
} else {
|
||||
drawRidgeRoof(this.id, this.canvas)
|
||||
}
|
||||
} else {
|
||||
drawRidgeRoof(this.id, this.canvas)
|
||||
}
|
||||
} else {
|
||||
drawRidgeRoof(this.id, this.canvas)
|
||||
}
|
||||
} else {
|
||||
drawRidgeRoof(this.id, this.canvas)
|
||||
@ -757,7 +790,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
setViewLengthText(isView) {
|
||||
this.canvas
|
||||
?.getObjects()
|
||||
.filter((obj) => obj.name === 'lengthText' && obj.parent === this)
|
||||
.filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
||||
.forEach((text) => {
|
||||
text.set({ visible: isView })
|
||||
})
|
||||
@ -770,7 +803,33 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
this.scaleY = scale
|
||||
this.addLengthText()
|
||||
},
|
||||
divideLine() {
|
||||
// splitPolygonWithLines(this)
|
||||
|
||||
calcOriginCoords() {
|
||||
const points = this.points
|
||||
const minX = Math.min(...points.map((p) => p.x))
|
||||
const maxX = Math.max(...points.map((p) => p.x))
|
||||
const minY = Math.min(...points.map((p) => p.y))
|
||||
const maxY = Math.max(...points.map((p) => p.y))
|
||||
|
||||
let left = 0
|
||||
let top = 0
|
||||
|
||||
if (this.originX === 'center') {
|
||||
left = (minX + maxX) / 2
|
||||
} else if (this.originX === 'left') {
|
||||
left = minX
|
||||
} else if (this.originX === 'right') {
|
||||
left = maxX
|
||||
}
|
||||
|
||||
if (this.originY === 'center') {
|
||||
top = (minY + maxY) / 2
|
||||
} else if (this.originY === 'top') {
|
||||
top = minY
|
||||
} else if (this.originY === 'bottom') {
|
||||
top = maxY
|
||||
}
|
||||
|
||||
return { left, top }
|
||||
},
|
||||
})
|
||||
|
||||
@ -16,11 +16,11 @@ import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/Panel
|
||||
|
||||
export default function CanvasFrame() {
|
||||
const canvasRef = useRef(null)
|
||||
const { canvas } = useCanvas('canvas')
|
||||
const { canvas, handleBackImageLoadToCanvas } = useCanvas('canvas')
|
||||
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
|
||||
const currentMenu = useRecoilValue(currentMenuState)
|
||||
const { contextMenu, handleClick } = useContextMenu()
|
||||
const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans } = usePlan()
|
||||
const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans, currentCanvasPlan } = usePlan()
|
||||
useEvent()
|
||||
|
||||
const loadCanvas = () => {
|
||||
|
||||
@ -33,6 +33,8 @@ import useMenu from '@/hooks/common/useMenu'
|
||||
import { MENU } from '@/common/common'
|
||||
|
||||
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
||||
import { estimateState } from '@/store/floorPlanObjectAtom'
|
||||
import DocDownOptionPop from '../estimate/popup/DocDownOptionPop'
|
||||
|
||||
export default function CanvasMenu(props) {
|
||||
const { menuNumber, setMenuNumber } = props
|
||||
@ -53,7 +55,10 @@ export default function CanvasMenu(props) {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const { handleZoomClear, handleZoom } = useCanvasEvent()
|
||||
const { handleMenu } = useMenu()
|
||||
|
||||
const { handleEstimateSubmit } = useEstimateController()
|
||||
const estimateRecoilState = useRecoilValue(estimateState)
|
||||
const [estimatePopupOpen, setEstimatePopupOpen] = useState(false)
|
||||
|
||||
const { getMessage } = useMessage()
|
||||
const { currentCanvasPlan, saveCanvas } = usePlan()
|
||||
@ -81,6 +86,9 @@ export default function CanvasMenu(props) {
|
||||
case 4:
|
||||
setType('module')
|
||||
break
|
||||
case 6:
|
||||
router.push(`/floor-plan/simulator/${menu.index}`)
|
||||
break
|
||||
}
|
||||
|
||||
if (pathname !== '/floor-plan') router.push('/floor-plan')
|
||||
@ -135,6 +143,38 @@ export default function CanvasMenu(props) {
|
||||
addPopup(id, 1, <SettingModal01 id={id} />, true)
|
||||
}
|
||||
|
||||
// 견적서 초기화 버튼
|
||||
const handleEstimateReset = () => {
|
||||
// console.log('estimateRecoilState::', estimateRecoilState)
|
||||
//objectNo, planNo
|
||||
swalFire({
|
||||
//저장된 견적서 정보가 초기화되고, 도면정보가 반영됩니다. 정말로 초기화 하시겠습니까?
|
||||
//물건정보
|
||||
text: getMessage('estimate.detail.reset.confirmMsg'),
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
console.log('내용초기화 및 변경일시 갱신')
|
||||
},
|
||||
denyFn: () => {
|
||||
console.log('초기화하지 않음. 변경일시 갱신안함')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 견적서 복사버튼
|
||||
* (견적서 번호(estimateRecoilState.docNo)가 생성된 이후 버튼 활성화 )
|
||||
* T01관리자 계정 및 1차판매점에게만 제공
|
||||
*/
|
||||
|
||||
const handleEstimateCopy = () => {
|
||||
// console.log('estimateRecoilState::', estimateRecoilState)
|
||||
//objectNo, planNo
|
||||
console.log('복사')
|
||||
console.log('물건정보+도면+견적서를 모두 복사')
|
||||
console.log('견적서 가격은 정가를 표시')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (globalLocale === 'ko') {
|
||||
setAppMessageState(KO)
|
||||
@ -225,7 +265,7 @@ export default function CanvasMenu(props) {
|
||||
{menuNumber === 5 && (
|
||||
<>
|
||||
<div className="ico-btn-from">
|
||||
<button className="btn-frame gray ico-flx act">
|
||||
<button className="btn-frame gray ico-flx" onClick={() => setEstimatePopupOpen(true)}>
|
||||
<span className="ico ico01"></span>
|
||||
<span>{getMessage('plan.menu.estimate.docDown')}</span>
|
||||
</button>
|
||||
@ -233,11 +273,24 @@ export default function CanvasMenu(props) {
|
||||
<span className="ico ico02"></span>
|
||||
<span>{getMessage('plan.menu.estimate.save')}</span>
|
||||
</button>
|
||||
<button className="btn-frame gray ico-flx">
|
||||
{/* {estimateRecoilState?.docNo != null && ( */}
|
||||
<button
|
||||
className="btn-frame gray ico-flx"
|
||||
onClick={() => {
|
||||
handleEstimateReset()
|
||||
}}
|
||||
>
|
||||
<span className="ico ico03"></span>
|
||||
<span>{getMessage('plan.menu.estimate.reset')}</span>
|
||||
</button>
|
||||
<button className="btn-frame gray ico-flx">
|
||||
{/* )} */}
|
||||
|
||||
<button
|
||||
className="btn-frame gray ico-flx"
|
||||
onClick={() => {
|
||||
handleEstimateCopy()
|
||||
}}
|
||||
>
|
||||
<span className="ico ico04"></span>
|
||||
<span>{getMessage('plan.menu.estimate.copy')}</span>
|
||||
</button>
|
||||
@ -263,6 +316,8 @@ export default function CanvasMenu(props) {
|
||||
<div className={`canvas-depth2-wrap ${menuNumber === 2 || menuNumber === 3 || menuNumber === 4 ? 'active' : ''}`}>
|
||||
{(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && <MenuDepth01 />}
|
||||
</div>
|
||||
{/* 견적서(menuNumber=== 5) 상세화면인경우 문서다운로드 팝업 */}
|
||||
{estimatePopupOpen && <DocDownOptionPop planNo={estimateRecoilState?.planNo} setEstimatePopupOpen={setEstimatePopupOpen} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,14 +5,18 @@ import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
|
||||
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
||||
export default function FlowDirectionSetting(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
const { id, pos = contextPopupPosition, target } = props
|
||||
const { getMessage } = useMessage()
|
||||
const { closePopup } = usePopup()
|
||||
const [compasDeg, setCompasDeg] = useState(360)
|
||||
const [flowDirection, setFlowDirection] = useState(target.direction)
|
||||
const { changeSurfaceFlowDirection } = useSurfaceShapeBatch()
|
||||
|
||||
const orientations = [
|
||||
// { name: `${getMessage('commons.none')}`, value: 0 },
|
||||
{ name: `${getMessage('commons.south')}`, value: 360 },
|
||||
{ name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 },
|
||||
{ name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: 45 },
|
||||
@ -75,13 +79,13 @@ export default function FlowDirectionSetting(props) {
|
||||
<div className="object-direction-wrap">
|
||||
<div className="plane-direction">
|
||||
<span className="top">{getMessage('commons.north')}</span>
|
||||
<button className="plane-btn up"></button>
|
||||
<button className={`plane-btn up ${flowDirection === 'north' ? 'act' : ''}`} onClick={() => setFlowDirection('north')}></button>
|
||||
<span className="right">{getMessage('commons.east')}</span>
|
||||
<button className="plane-btn right"></button>
|
||||
<button className={`plane-btn right ${flowDirection === 'east' ? 'act' : ''}`} onClick={() => setFlowDirection('east')}></button>
|
||||
<span className="bottom">{getMessage('commons.south')}</span>
|
||||
<button className="plane-btn down act"></button>
|
||||
<button className={`plane-btn down ${flowDirection === 'south' ? 'act' : ''}`} onClick={() => setFlowDirection('south')}></button>
|
||||
<span className="left">{getMessage('commons.west')}</span>
|
||||
<button className="plane-btn left"></button>
|
||||
<button className={`plane-btn left ${flowDirection === 'west' ? 'act' : ''}`} onClick={() => setFlowDirection('west')}></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -111,18 +115,14 @@ export default function FlowDirectionSetting(props) {
|
||||
key={index}
|
||||
className={`circle ${compasDeg === 15 * (12 + index) ? 'act' : ''}`}
|
||||
onClick={() => setCompasDeg(15 * (12 + index))}
|
||||
>
|
||||
<i>{13 - index}</i>
|
||||
</div>
|
||||
></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>
|
||||
))}
|
||||
<div className="compas">
|
||||
<div className="compas-arr" style={{ transform: `rotate(${compasDeg}deg)` }}></div>
|
||||
@ -133,7 +133,9 @@ export default function FlowDirectionSetting(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-btn-wrap">
|
||||
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
|
||||
<button className="btn-frame modal act" onClick={() => changeSurfaceFlowDirection(target, flowDirection, selectedOrientation)}>
|
||||
{getMessage('modal.common.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,13 +3,18 @@ import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
|
||||
export default function LinePropertySetting(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
const { id, pos = contextPopupPosition } = props
|
||||
const { id, pos = contextPopupPosition, target } = props
|
||||
const { getMessage } = useMessage()
|
||||
const { closePopup } = usePopup()
|
||||
const { changeSurfaceLinePropertyEvent, changeSurfaceLineProperty } = useSurfaceShapeBatch()
|
||||
const { initEvent } = useEvent()
|
||||
|
||||
const properties = [
|
||||
{ name: getMessage('eaves.line'), value: 'eaves' },
|
||||
{ name: getMessage('ridge'), value: 'ridge' },
|
||||
@ -29,6 +34,14 @@ export default function LinePropertySetting(props) {
|
||||
{ name: getMessage('no.setting'), value: 'noSetting' },
|
||||
]
|
||||
const [selectedProperty, setSelectedProperty] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
changeSurfaceLinePropertyEvent(target)
|
||||
return () => {
|
||||
initEvent()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<WithDraggable isShow={true} pos={pos}>
|
||||
<div className={`modal-pop-wrap r mount`}>
|
||||
@ -66,7 +79,9 @@ export default function LinePropertySetting(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-btn-wrap">
|
||||
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
|
||||
<button className="btn-frame modal act" onClick={() => changeSurfaceLineProperty(selectedProperty)}>
|
||||
{getMessage('modal.common.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@ export default function SizeSetting(props) {
|
||||
const { getMessage } = useMessage()
|
||||
const { closePopup } = usePopup()
|
||||
const { resizeObjectBatch } = useObjectBatch({})
|
||||
const { reSizePolygon } = useSurfaceShapeBatch()
|
||||
const { resizeSurfaceShapeBatch } = useSurfaceShapeBatch()
|
||||
const widthRef = useRef(null)
|
||||
const heightRef = useRef(null)
|
||||
|
||||
@ -36,10 +36,11 @@ export default function SizeSetting(props) {
|
||||
target.name === BATCH_TYPE.OPENING ||
|
||||
target.name === BATCH_TYPE.SHADOW ||
|
||||
target.name === BATCH_TYPE.TRIANGLE_DORMER ||
|
||||
target.name === BATCH_TYPE.PENTAGON_DORMER ||
|
||||
target.name === POLYGON_TYPE.ROOF
|
||||
target.name === BATCH_TYPE.PENTAGON_DORMER
|
||||
) {
|
||||
resizeObjectBatch(settingTarget, target, width, height)
|
||||
} else if (target.name === POLYGON_TYPE.ROOF) {
|
||||
resizeSurfaceShapeBatch(settingTarget, target, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
|
||||
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { Fragment, useEffect, useState } from 'react'
|
||||
|
||||
import { canvasSettingState } from '@/store/canvasAtom'
|
||||
import { basicSettingState } from '@/store/settingAtom'
|
||||
|
||||
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'
|
||||
import useRefFiles from '@/hooks/common/useRefFiles'
|
||||
import { usePlan } from '@/hooks/usePlan'
|
||||
|
||||
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
|
||||
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import { SessionContext } from '@/app/SessionProvider'
|
||||
|
||||
export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) {
|
||||
const [objectNo, setObjectNo] = useState('test123241008001') // 후에 삭제 필요
|
||||
@ -18,7 +24,20 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
|
||||
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
|
||||
const { closePopup } = usePopup()
|
||||
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
|
||||
const [image, setImage] = useState(null)
|
||||
const {
|
||||
refImage,
|
||||
queryRef,
|
||||
setRefImage,
|
||||
handleRefFile,
|
||||
refFileMethod,
|
||||
setRefFileMethod,
|
||||
handleRefFileMethod,
|
||||
mapPositionAddress,
|
||||
setMapPositionAddress,
|
||||
handleFileDelete,
|
||||
handleMapImageDown,
|
||||
} = useRefFiles()
|
||||
const { currentCanvasPlan } = usePlan()
|
||||
|
||||
const { getMessage } = useMessage()
|
||||
const { get, post } = useAxios()
|
||||
@ -487,19 +506,85 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
|
||||
<tr>
|
||||
<th>{getMessage('common.input.file')}</th>
|
||||
<td>
|
||||
<div className="flex-box">
|
||||
<div className="pop-form-radio mb10">
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio03"
|
||||
id="ra06"
|
||||
value={'1'}
|
||||
onChange={(e) => handleRefFileMethod(e)}
|
||||
checked={refFileMethod === '1'}
|
||||
/>
|
||||
<label htmlFor="ra06">ファイルを読み込む</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio03"
|
||||
id="ra07"
|
||||
value={'2'}
|
||||
onChange={(e) => handleRefFileMethod(e)}
|
||||
checked={refFileMethod === '2'}
|
||||
/>
|
||||
<label htmlFor="ra07">アドレスを読み込む</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 파일 불러오기 */}
|
||||
{refFileMethod === '1' && (
|
||||
<div className="flex-box">
|
||||
<div className="img-edit-wrap">
|
||||
<label className="img-edit-btn" htmlFor="img_file">
|
||||
<span className="img-edit"></span>
|
||||
{getMessage('common.input.file.load')}
|
||||
</label>
|
||||
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
|
||||
</div>
|
||||
<div className="img-name-wrap">
|
||||
{currentCanvasPlan?.bgImageName === null ? (
|
||||
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
|
||||
) : (
|
||||
<input type="text" className="input-origin al-l" value={currentCanvasPlan?.bgImageName} readOnly />
|
||||
)}
|
||||
{(refImage || currentCanvasPlan?.bgImageName) && <button className="img-check" onClick={handleFileDelete}></button>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 주소 불러오기 */}
|
||||
{refFileMethod === '2' && (
|
||||
<div className="flex-box for-address">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin al-l mr10"
|
||||
placeholder={'住所入力'}
|
||||
ref={queryRef}
|
||||
value={mapPositionAddress}
|
||||
onChange={(e) => setMapPositionAddress(e.target.value)}
|
||||
/>
|
||||
<div className="img-edit-wrap mr5">
|
||||
<button className="img-edit-btn" onClick={handleMapImageDown}>
|
||||
完了
|
||||
</button>
|
||||
</div>
|
||||
{mapPositionAddress && <span className="check-address fail"></span>}
|
||||
{/* <span className="check-address success"></span> */}
|
||||
</div>
|
||||
)}
|
||||
{/* <div className="flex-box">
|
||||
<div className="img-edit-wrap">
|
||||
<label className="img-edit-btn" htmlFor="img_file">
|
||||
<span className="img-edit"></span>
|
||||
{getMessage('common.input.file.load')}
|
||||
</label>
|
||||
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => setImage(e.target.files[0])} />
|
||||
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
|
||||
</div>
|
||||
<div className="img-name-wrap">
|
||||
<input type="text" className="input-origin al-l" defaultValue={''} value={image ? image.name : ''} readOnly />
|
||||
{image && <button className="img-check" onClick={() => setImage(null)}></button>}
|
||||
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
|
||||
{refImage && <button className="img-check" onClick={() => setRefImage(null)}></button>}
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { currentObjectState } from '@/store/canvasAtom'
|
||||
import { useRoofAllocationSetting } from '@/hooks/roofcover/useRoofAllocationSetting'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
|
||||
export default function ActualSizeSetting(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
const { id, pos = contextPopupPosition } = props
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const [planeSize, setPlaneSize] = useState(0)
|
||||
const [actualSize, setActualSize] = useState(0)
|
||||
const { setLineSize, handleAlloc } = useRoofAllocationSetting()
|
||||
const { closePopup } = usePopup()
|
||||
const { getMessage } = useMessage()
|
||||
const { swalFire } = useSwal()
|
||||
useEffect(() => {
|
||||
if (currentObject && currentObject.attributes) {
|
||||
setPlaneSize(currentObject.attributes.planeSize ?? 0)
|
||||
setActualSize(currentObject.attributes.actualSize ?? 0)
|
||||
}
|
||||
}, [currentObject])
|
||||
|
||||
const handleFinish = () => {
|
||||
swalFire({
|
||||
text: '완료 하시겠습니까?',
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
handleAlloc()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
swalFire({
|
||||
text: '완료 하시겠습니까?',
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
handleAlloc()
|
||||
},
|
||||
// denyFn: () => {
|
||||
// swalFire({ text: '취소되었습니다.', icon: 'error' })
|
||||
// },
|
||||
})
|
||||
}
|
||||
|
||||
const handleApply = () => {
|
||||
if (!currentObject) {
|
||||
swalFire({ type: 'alert', icon: 'error', text: getMessage('modal.actual.size.setting.not.exist.auxiliary.line') })
|
||||
return
|
||||
}
|
||||
if (actualSize !== 0) {
|
||||
setLineSize(currentObject?.id, actualSize)
|
||||
} else {
|
||||
swalFire({ type: 'alert', icon: 'error', text: getMessage('modal.actual.size.setting.not.exist.size') })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<WithDraggable isShow={true} pos={pos}>
|
||||
<div className={`modal-pop-wrap ssm mount`}>
|
||||
<div className="modal-head">
|
||||
<h1 className="title">{getMessage('modal.actual.size.setting')}</h1>
|
||||
<button className="modal-close" onClick={() => closePopup(id)}>
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="guide">
|
||||
<span>{getMessage('modal.actual.size.setting.info')}</span>
|
||||
</div>
|
||||
<div className="properties-setting-wrap outer">
|
||||
<div className="setting-tit">{getMessage('setting')}</div>
|
||||
<div className="outline-wrap">
|
||||
<div className="eaves-keraba-table">
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.actual.size.setting.plane.size.length')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5">
|
||||
<input type="text" className="input-origin block" value={planeSize} readOnly={true} />
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.actual.size.setting.actual.size.length')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr5">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
value={actualSize}
|
||||
onChange={(e) => setActualSize(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-btn-wrap">
|
||||
<button className="btn-frame modal mr5" onClick={handleFinish}>
|
||||
{getMessage('common.setting.finish')}
|
||||
</button>
|
||||
<button className="btn-frame modal act" onClick={handleApply}>
|
||||
{getMessage('apply')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WithDraggable>
|
||||
)
|
||||
}
|
||||
@ -9,13 +9,11 @@ import { useRecoilValue, useRecoilState, useSetRecoilState, useResetRecoilState
|
||||
import { stuffSearchState } from '@/store/stuffAtom'
|
||||
import { queryStringFormatter, isEmptyArray } from '@/util/common-utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
import { convertNumberToPriceDecimal } from '@/util/common-utils'
|
||||
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
|
||||
import KO from '@/locales/ko.json'
|
||||
import JA from '@/locales/ja.json'
|
||||
import QPagination from '../common/pagination/QPagination'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { SessionContext } from '@/app/SessionProvider'
|
||||
|
||||
export default function Stuff() {
|
||||
@ -192,7 +190,11 @@ export default function Stuff() {
|
||||
})
|
||||
}
|
||||
|
||||
//if (session.storeId === 'T01') {
|
||||
fetchData()
|
||||
//} else if (stuffSearch.schSelSaleStoreId !== '') {
|
||||
//fetchData()
|
||||
//}
|
||||
} else if (stuffSearchParams?.code === 'M') {
|
||||
const params = {
|
||||
saleStoreId: session?.storeId,
|
||||
|
||||
@ -59,7 +59,7 @@ export default function StuffDetail() {
|
||||
standardWindSpeedId: '', //기준풍속
|
||||
verticalSnowCover: '', //수직적설량
|
||||
coldRegionFlg: false, //한랭지대책시행(true : 1 / false : 0)
|
||||
surfaceType: 'III・IV', //면조도구분(III・IV / Ⅱ)
|
||||
surfaceType: 'Ⅲ・Ⅳ', //면조도구분(Ⅲ・Ⅳ / Ⅱ)
|
||||
saltAreaFlg: false, //염해지역용아이템사용 (true : 1 / false : 0)
|
||||
installHeight: '', //설치높이
|
||||
conType: '0', //계약조건(잉여 / 전량)
|
||||
@ -550,7 +550,7 @@ export default function StuffDetail() {
|
||||
|
||||
//면조도구분 surfaceType null로 내려오면 셋팅 안하고 저장할때 필수값 체크하도록
|
||||
// form.setValue('surfaceType', 'Ⅱ')
|
||||
// form.setValue('surfaceType', 'III・IV')
|
||||
// form.setValue('surfaceType', 'Ⅲ・Ⅳ')
|
||||
form.setValue('surfaceType', detailData.surfaceType)
|
||||
//염해지역용아이템사용 saltAreaFlg 1이면 true
|
||||
form.setValue('saltAreaFlg', detailData.saltAreaFlg === '1' ? true : false)
|
||||
@ -872,6 +872,9 @@ export default function StuffDetail() {
|
||||
form.setValue('standardWindSpeedId', `WL_${info.windSpeed}`)
|
||||
form.setValue('verticalSnowCover', info.verticalSnowCover)
|
||||
form.setValue('surfaceType', info.surfaceType)
|
||||
if (info.surfaceType === 'Ⅱ') {
|
||||
form.setValue('saltAreaFlg', true)
|
||||
}
|
||||
form.setValue('installHeight', info.installHeight)
|
||||
form.setValue('remarks', info.remarks)
|
||||
|
||||
@ -893,6 +896,15 @@ export default function StuffDetail() {
|
||||
form.setValue('standardWindSpeedId', info.windSpeed)
|
||||
}
|
||||
|
||||
//면조도구분surfaceType & 염해지역용아이템사용saltAreaFlg 컨트롤
|
||||
const handleRadioChange = (e) => {
|
||||
if (e.target.value === 'Ⅱ') {
|
||||
form.setValue('saltAreaFlg', true)
|
||||
} else {
|
||||
form.setValue('saltAreaFlg', false)
|
||||
}
|
||||
}
|
||||
|
||||
//receiveUser: '', //담당자
|
||||
const _receiveUser = watch('receiveUser')
|
||||
//objectName: '', //물건명
|
||||
@ -1561,7 +1573,7 @@ export default function StuffDetail() {
|
||||
onChange={onSelectionChange2}
|
||||
getOptionLabel={(x) => x.saleStoreName}
|
||||
getOptionValue={(x) => x.saleStoreId}
|
||||
isDisabled={otherSaleStoreList.length > 1 ? false : true}
|
||||
isDisabled={otherSaleStoreList.length > 0 ? false : true}
|
||||
isClearable={true}
|
||||
value={otherSaleStoreList.filter(function (option) {
|
||||
return option.saleStoreId === otherSelOptions
|
||||
@ -1715,11 +1727,29 @@ export default function StuffDetail() {
|
||||
<td>
|
||||
<div className="flx-box">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input type="radio" name="surfaceType" value="III・IV" id="surfaceType0" {...form.register('surfaceType')} />
|
||||
<label htmlFor="surfaceType0">III・IV</label>
|
||||
<input
|
||||
type="radio"
|
||||
name="surfaceType"
|
||||
value="Ⅲ・Ⅳ"
|
||||
id="surfaceType0"
|
||||
{...form.register('surfaceType')}
|
||||
onChange={(e) => {
|
||||
handleRadioChange(e)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="surfaceType0">Ⅲ・Ⅳ</label>
|
||||
</div>
|
||||
<div className="d-check-radio light mr10">
|
||||
<input type="radio" name="surfaceType" value="Ⅱ" id="surfaceType1" {...form.register('surfaceType')} />
|
||||
<input
|
||||
type="radio"
|
||||
name="surfaceType"
|
||||
value="Ⅱ"
|
||||
id="surfaceType1"
|
||||
{...form.register('surfaceType')}
|
||||
onChange={(e) => {
|
||||
handleRadioChange(e)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="surfaceType1">Ⅱ</label>
|
||||
</div>
|
||||
<div className="d-check-box light mr5">
|
||||
@ -2211,11 +2241,29 @@ export default function StuffDetail() {
|
||||
<td>
|
||||
<div className="flx-box">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input type="radio" name="surfaceType" value="III・IV" id="surfaceType0" {...form.register('surfaceType')} />
|
||||
<label htmlFor="surfaceType0">III・IV</label>
|
||||
<input
|
||||
type="radio"
|
||||
name="surfaceType"
|
||||
value="Ⅲ・Ⅳ"
|
||||
id="surfaceType0"
|
||||
{...form.register('surfaceType')}
|
||||
onChange={(e) => {
|
||||
handleRadioChange(e)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="surfaceType0">Ⅲ・Ⅳ</label>
|
||||
</div>
|
||||
<div className="d-check-radio light mr10">
|
||||
<input type="radio" name="surfaceType" value="Ⅱ" id="surfaceType1" {...form.register('surfaceType')} />
|
||||
<input
|
||||
type="radio"
|
||||
name="surfaceType"
|
||||
value="Ⅱ"
|
||||
id="surfaceType1"
|
||||
{...form.register('surfaceType')}
|
||||
onChange={(e) => {
|
||||
handleRadioChange(e)
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="surfaceType1">Ⅱ</label>
|
||||
</div>
|
||||
<div className="d-check-box light mr5">
|
||||
|
||||
@ -12,7 +12,6 @@ import { isEmptyArray } from '@/util/common-utils'
|
||||
import dayjs from 'dayjs'
|
||||
import Link from 'next/link'
|
||||
import SingleDatePicker from '../common/datepicker/SingleDatePicker'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
|
||||
@ -20,7 +19,6 @@ import { SessionContext } from '@/app/SessionProvider'
|
||||
|
||||
export default function StuffSearchCondition() {
|
||||
const { session } = useContext(SessionContext)
|
||||
const sessionState = useRecoilValue(sessionStore)
|
||||
const setAppMessageState = useSetRecoilState(appMessageStore)
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const { getMessage } = useMessage()
|
||||
@ -163,7 +161,7 @@ export default function StuffSearchCondition() {
|
||||
} else {
|
||||
if (session.storeLvl === '1') {
|
||||
//T01아닌 1차점일때
|
||||
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${sessionState?.userId}`
|
||||
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}`
|
||||
} else {
|
||||
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}`
|
||||
}
|
||||
@ -186,34 +184,36 @@ export default function StuffSearchCondition() {
|
||||
setSchSelSaleStoreList(allList)
|
||||
setFavoriteStoreList(favList)
|
||||
setShowSaleStoreList(favList)
|
||||
setSchSelSaleStoreId(session?.storeId)
|
||||
// setSchSelSaleStoreId(session?.storeId)
|
||||
setStuffSearch({
|
||||
...stuffSearch,
|
||||
code: 'S',
|
||||
schSelSaleStoreId: session?.storeId,
|
||||
// schSelSaleStoreId: session?.storeId,
|
||||
})
|
||||
|
||||
//T01일때 2차 판매점 호출하기 디폴트로 1차점을 본인으로 셋팅해서 세션storeId사용
|
||||
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}`
|
||||
// 디폴트 셋팅 안하기로
|
||||
// url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}`
|
||||
|
||||
get({ url: url }).then((res) => {
|
||||
if (!isEmptyArray(res)) {
|
||||
res.map((row) => {
|
||||
row.value = row.saleStoreId
|
||||
row.label = row.saleStoreName
|
||||
})
|
||||
// get({ url: url }).then((res) => {
|
||||
// if (!isEmptyArray(res)) {
|
||||
// res.map((row) => {
|
||||
// row.value = row.saleStoreId
|
||||
// row.label = row.saleStoreName
|
||||
// })
|
||||
|
||||
otherList = res
|
||||
setOtherSaleStoreList(otherList)
|
||||
} else {
|
||||
setOtherSaleStoreList([])
|
||||
}
|
||||
})
|
||||
// otherList = res
|
||||
// setOtherSaleStoreList(otherList)
|
||||
// } else {
|
||||
// setOtherSaleStoreList([])
|
||||
// }
|
||||
// })
|
||||
} else {
|
||||
if (session?.storeLvl === '1') {
|
||||
allList = res
|
||||
favList = res.filter((row) => row.priority !== 'B')
|
||||
otherList = res.filter((row) => row.firstAgentYn === 'N')
|
||||
|
||||
setSchSelSaleStoreList(allList)
|
||||
setFavoriteStoreList(allList)
|
||||
setShowSaleStoreList(allList)
|
||||
@ -323,6 +323,12 @@ export default function StuffSearchCondition() {
|
||||
useEffect(() => {
|
||||
setStartDate(stuffSearch?.schFromDt ? stuffSearch.schFromDt : dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'))
|
||||
setEndDate(stuffSearch?.schToDt ? stuffSearch.schToDt : dayjs(new Date()).format('YYYY-MM-DD'))
|
||||
setObjectNo(stuffSearch.schObjectNo ? stuffSearch.schObjectNo : objectNo)
|
||||
setSaleStoreName(stuffSearch.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName)
|
||||
setAddress(stuffSearch.schAddress ? stuffSearch.schAddress : address)
|
||||
setobjectName(stuffSearch.schObjectName ? stuffSearch.schObjectName : objectName)
|
||||
setDispCompanyName(stuffSearch.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName)
|
||||
setReceiveUser(stuffSearch.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser)
|
||||
}, [stuffSearch])
|
||||
|
||||
useEffect(() => {
|
||||
@ -385,7 +391,7 @@ export default function StuffSearchCondition() {
|
||||
ref={objectNoRef}
|
||||
className="input-light"
|
||||
defaultValue={stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo}
|
||||
onChange={(e) => {
|
||||
onChange={() => {
|
||||
setObjectNo(objectNoRef.current.value)
|
||||
}}
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
@ -400,7 +406,7 @@ export default function StuffSearchCondition() {
|
||||
ref={saleStoreNameRef}
|
||||
className="input-light"
|
||||
defaultValue={stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName}
|
||||
onChange={(e) => {
|
||||
onChange={() => {
|
||||
setSaleStoreName(saleStoreNameRef.current.value)
|
||||
}}
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
@ -415,7 +421,7 @@ export default function StuffSearchCondition() {
|
||||
ref={addressRef}
|
||||
className="input-light"
|
||||
defaultValue={stuffSearch?.schAddress ? stuffSearch.schAddress : address}
|
||||
onChange={(e) => {
|
||||
onChange={() => {
|
||||
setAddress(addressRef.current.value)
|
||||
}}
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
@ -430,7 +436,7 @@ export default function StuffSearchCondition() {
|
||||
ref={dispCompanyNameRef}
|
||||
className="input-light"
|
||||
defaultValue={stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName}
|
||||
onChange={(e) => {
|
||||
onChange={() => {
|
||||
setDispCompanyName(dispCompanyNameRef.current.value)
|
||||
}}
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
@ -447,7 +453,7 @@ export default function StuffSearchCondition() {
|
||||
ref={objectNameRef}
|
||||
className="input-light"
|
||||
defaultValue={stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName}
|
||||
onChange={(e) => {
|
||||
onChange={() => {
|
||||
setobjectName(objectNameRef.current.value)
|
||||
}}
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
@ -462,7 +468,7 @@ export default function StuffSearchCondition() {
|
||||
className="input-light"
|
||||
ref={receiveUserRef}
|
||||
defaultValue={stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser}
|
||||
onChange={(e) => {
|
||||
onChange={() => {
|
||||
setReceiveUser(receiveUserRef.current.value)
|
||||
}}
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
@ -580,7 +586,7 @@ export default function StuffSearchCondition() {
|
||||
onChange={onSelectionChange2}
|
||||
getOptionLabel={(x) => x.saleStoreName}
|
||||
getOptionValue={(x) => x.saleStoreId}
|
||||
isDisabled={otherSaleStoreList.length > 1 ? false : true}
|
||||
isDisabled={otherSaleStoreList.length > 0 ? false : true}
|
||||
isClearable={true}
|
||||
value={otherSaleStoreList.filter(function (option) {
|
||||
return option.saleStoreId === otherSaleStoreId
|
||||
|
||||
365
src/components/simulator/Simulator.jsx
Normal file
365
src/components/simulator/Simulator.jsx
Normal file
@ -0,0 +1,365 @@
|
||||
'use client'
|
||||
|
||||
import 'chart.js/auto'
|
||||
import { Bar } from 'react-chartjs-2'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePlan } from '@/hooks/usePlan'
|
||||
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||
|
||||
import { convertNumberToPriceDecimal } from '@/util/common-utils'
|
||||
|
||||
export default function Simulator() {
|
||||
const { plans } = usePlan()
|
||||
const plan = plans.find((plan) => plan.isCurrent === true)
|
||||
|
||||
const chartRef = useRef(null)
|
||||
|
||||
// recoil 물건번호
|
||||
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||
const [objectNo, setObjectNo] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setObjectNo(objectRecoil.floorPlanObjectNo)
|
||||
}, [objectRecoil])
|
||||
|
||||
// 캔버스 메뉴 넘버 셋팅
|
||||
const { setMenuNumber } = useCanvasMenu()
|
||||
|
||||
useEffect(() => {
|
||||
setMenuNumber(6)
|
||||
}, [])
|
||||
|
||||
const { get } = useAxios()
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
// 차트 관련
|
||||
const [chartData, setChartData] = useState([])
|
||||
const data = {
|
||||
labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'kWh',
|
||||
data: chartData.slice(0, 12),
|
||||
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
'rgba(54, 162, 235, 0.2)',
|
||||
'rgba(255, 206, 86, 0.2)',
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(153, 102, 255, 0.2)',
|
||||
'rgba(255, 159, 64, 0.2)',
|
||||
'rgba(0, 99, 132, 0.2)',
|
||||
'rgba(0, 162, 235, 0.2)',
|
||||
'rgba(0, 206, 86, 0.2)',
|
||||
'rgba(0, 192, 192, 0.2)',
|
||||
'rgba(0, 102, 255, 0.2)',
|
||||
'rgba(0, 159, 64, 0.2)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
'rgba(54, 162, 235, 0.2)',
|
||||
'rgba(255, 206, 86, 0.2)',
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(153, 102, 255, 0.2)',
|
||||
'rgba(255, 159, 64, 0.2)',
|
||||
'rgba(0, 99, 132, 0.2)',
|
||||
'rgba(0, 162, 235, 0.2)',
|
||||
'rgba(0, 206, 86, 0.2)',
|
||||
'rgba(0, 192, 192, 0.2)',
|
||||
'rgba(0, 102, 255, 0.2)',
|
||||
'rgba(0, 159, 64, 0.2)',
|
||||
],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const options = {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (objectNo) {
|
||||
fetchObjectDetail(objectNo)
|
||||
}
|
||||
fetchSimulatorNotice()
|
||||
}, [objectNo, plan])
|
||||
|
||||
// 물건 상세 정보 조회
|
||||
const [objectDetail, setObjectDetail] = useState({})
|
||||
|
||||
// 모듈배치정보 조회
|
||||
const [moduleInfoList, setModuleInfoList] = useState([])
|
||||
|
||||
// 파워컨디셔너 조회
|
||||
const [pcsInfoList, setPcsInfoList] = useState([])
|
||||
|
||||
const fetchObjectDetail = async (objectNo) => {
|
||||
const apiUrl = `/api/pwrGnrSimulation/calculations?objectNo=${objectNo}&planNo=${plan?.id}`
|
||||
const resultData = await get({ url: apiUrl })
|
||||
if (resultData) {
|
||||
setObjectDetail(resultData)
|
||||
if (resultData.frcPwrGnrList) {
|
||||
setChartData(resultData.frcPwrGnrList)
|
||||
}
|
||||
if (resultData.pcsList) {
|
||||
setPcsInfoList(resultData.pcsList)
|
||||
}
|
||||
if (resultData.roofModuleList) {
|
||||
setModuleInfoList(resultData.roofModuleList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 시뮬레이션 안내사항 조회
|
||||
const [content, setContent] = useState('')
|
||||
|
||||
const fetchSimulatorNotice = async () => {
|
||||
get({ url: '/api/pwrGnrSimulation/guideInfo' }).then((res) => {
|
||||
if (res.data) {
|
||||
setContent(res.data.replaceAll('\n', '<br/>'))
|
||||
} else {
|
||||
setContent(getMessage('common.message.no.data'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sub-content estimate">
|
||||
<div className="sub-content-inner">
|
||||
<div className="sub-content-box">
|
||||
<div className="sub-table-box">
|
||||
<div className="estimate-list-wrap">
|
||||
{/* 물건번호 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub1')}</div>
|
||||
<div className="estimate-name">
|
||||
{objectDetail.objectNo} (Plan No: {objectDetail.planNo})
|
||||
</div>
|
||||
</div>
|
||||
{/* 작성일 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub2')}</div>
|
||||
<div className="estimate-name">{`${dayjs(objectDetail.drawingEstimateCreateDate).format('YYYY.MM.DD')}`}</div>
|
||||
</div>
|
||||
{/* 시스템용량 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub3')}</div>
|
||||
<div className="estimate-name">{convertNumberToPriceDecimal(objectDetail.capacity)}kW</div>
|
||||
</div>
|
||||
{/* 연간예측발전량 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub4')}</div>
|
||||
<div className="estimate-name">{convertNumberToPriceDecimal(objectDetail.anlFrcsGnrt)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="estimate-list-wrap">
|
||||
{/* 도도부현 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub5')}</div>
|
||||
<div className="estimate-name">{objectDetail.prefName}</div>
|
||||
</div>
|
||||
{/* 일사량 관측지점 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub6')}</div>
|
||||
<div className="estimate-name">{objectDetail.areaName}</div>
|
||||
</div>
|
||||
{/* 적설조건 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub7')}</div>
|
||||
<div className="estimate-name">{objectDetail.snowfall}</div>
|
||||
</div>
|
||||
{/* 풍속조건 */}
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('simulator.title.sub8')}</div>
|
||||
<div className="estimate-name">{objectDetail.standardWindSpeedId}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sub-content-box">
|
||||
<div className="chart-wrap">
|
||||
<div className="chart-inner">
|
||||
<div className="sub-table-box">
|
||||
<div className="chart-box">
|
||||
<Bar ref={chartRef} data={data} options={options} />
|
||||
</div>
|
||||
<div className="table-box-title-wrap">
|
||||
<div className="title-wrap">
|
||||
<h3>{getMessage('simulator.table.sub9')} </h3>
|
||||
</div>
|
||||
</div>
|
||||
{/* 예측발전량 */}
|
||||
<div className="chart-month-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>1月</th>
|
||||
<th>2月</th>
|
||||
<th>3月</th>
|
||||
<th>4月</th>
|
||||
<th>5月</th>
|
||||
<th>6月</th>
|
||||
<th>7月</th>
|
||||
<th>8月</th>
|
||||
<th>9月</th>
|
||||
<th>10月</th>
|
||||
<th>11月</th>
|
||||
<th>12月</th>
|
||||
<th>{getMessage('simulator.table.sub6')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{chartData.length > 0 ? (
|
||||
<tr>
|
||||
{chartData.map((data) => (
|
||||
<td key={data}>{convertNumberToPriceDecimal(data)}</td>
|
||||
))}
|
||||
</tr>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={13}>{getMessage('common.message.no.data')}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chart-table-wrap">
|
||||
<div className="sub-table-box">
|
||||
<div className="module-table ">
|
||||
<table className="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{getMessage('simulator.table.sub1')}</th>
|
||||
<th>{getMessage('simulator.table.sub2')}</th>
|
||||
<th>{getMessage('simulator.table.sub3')}</th>
|
||||
<th>{getMessage('simulator.table.sub4')}</th>
|
||||
<th>{getMessage('simulator.table.sub5')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{moduleInfoList.length > 0 ? (
|
||||
moduleInfoList.map((moduleInfo) => {
|
||||
return (
|
||||
<>
|
||||
<tr key={moduleInfo.itemId}>
|
||||
{/* 지붕면 */}
|
||||
<td>{moduleInfo.roofSurface}</td>
|
||||
{/* 경사각 */}
|
||||
<td>{convertNumberToPriceDecimal(moduleInfo.slope)}寸</td>
|
||||
{/* 방위각(도) */}
|
||||
<td>{convertNumberToPriceDecimal(moduleInfo.angle)}</td>
|
||||
{/* 태양전지모듈 */}
|
||||
<td>
|
||||
<div className="overflow-lab">{moduleInfo.itemNo}</div>
|
||||
</td>
|
||||
{/* 매수 */}
|
||||
<td>{moduleInfo.amount}</td>
|
||||
</tr>
|
||||
</>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={5}>{getMessage('common.message.no.data')}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
{moduleInfoList.length > 0 && (
|
||||
<div className="module-total">
|
||||
<div className="total-title">{getMessage('simulator.table.sub6')}</div>
|
||||
<div className="total-num">
|
||||
{moduleInfoList.reduce((acc, moduleInfo) => convertNumberToPriceDecimal(Number(acc) + Number(moduleInfo.amount)), 0)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sub-table-box">
|
||||
<div className="module-table ">
|
||||
<table className="big">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{getMessage('simulator.table.sub7')}</th>
|
||||
<th>{getMessage('simulator.table.sub8')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pcsInfoList.length > 0 ? (
|
||||
pcsInfoList.map((pcsInfo) => {
|
||||
return (
|
||||
<>
|
||||
<tr key={pcsInfo.itemId}>
|
||||
{/* 파워컨디셔너 */}
|
||||
<td className="al-l">
|
||||
<div className="overflow-lab">{pcsInfo.itemNo}</div>
|
||||
</td>
|
||||
{/* 대 */}
|
||||
<td>{pcsInfo.amount}</td>
|
||||
</tr>
|
||||
</>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={2}>{getMessage('common.message.no.data')}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sub-content-box">
|
||||
<div className="sub-table-box">
|
||||
<div className="simulation-guide-wrap">
|
||||
<div className="simulation-tit-wrap">
|
||||
<span>
|
||||
{getMessage('simulator.notice.sub1')}
|
||||
<br />
|
||||
{getMessage('simulator.notice.sub2')}
|
||||
</span>
|
||||
</div>
|
||||
{/* 시뮬레이션 안내사항 */}
|
||||
<div
|
||||
className="simulation-guide-box"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: content,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -7,6 +7,7 @@ import { useFont } from '@/hooks/common/useFont'
|
||||
import { useGrid } from '@/hooks/common/useGrid'
|
||||
import { globalFontAtom } from '@/store/fontAtom'
|
||||
import { useRoof } from '@/hooks/common/useRoof'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
|
||||
export function useCanvasConfigInitialize() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -20,6 +21,7 @@ export function useCanvasConfigInitialize() {
|
||||
const {} = useFont()
|
||||
const {} = useGrid()
|
||||
const {} = useRoof()
|
||||
const { drawDirectionArrow } = usePolygon()
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvas) return
|
||||
@ -173,6 +175,7 @@ export function useCanvasConfigInitialize() {
|
||||
|
||||
//그룹 객체로 다시 만든다 (좌표때문에)
|
||||
const group = new fabric.Group(objectArray, {
|
||||
...objectArray,
|
||||
groupId: id,
|
||||
name: objectsName,
|
||||
selectable: true,
|
||||
|
||||
112
src/hooks/common/useRefFiles.js
Normal file
112
src/hooks/common/useRefFiles.js
Normal file
@ -0,0 +1,112 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { convertDwgToPng } from '@/lib/cadAction'
|
||||
import { useAxios } from '../useAxios'
|
||||
import { currentCanvasPlanState } from '@/store/canvasAtom'
|
||||
|
||||
export default function useRefFiles() {
|
||||
const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL
|
||||
const [refImage, setRefImage] = useState(null)
|
||||
const [refFileMethod, setRefFileMethod] = useState('1')
|
||||
const [mapPositionAddress, setMapPositionAddress] = useState('')
|
||||
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
|
||||
const queryRef = useRef(null)
|
||||
|
||||
const { swalFire } = useSwal()
|
||||
const { get, promisePut } = useAxios()
|
||||
// const { currentCanvasPlan, setCurrentCanvasPlan } = usePlan()
|
||||
|
||||
/**
|
||||
* 파일 불러오기 버튼 컨트롤
|
||||
* @param {*} file
|
||||
*/
|
||||
const handleRefFile = (file) => {
|
||||
setRefImage(file)
|
||||
file.name.split('.').pop() === 'dwg' ? handleUploadRefFile(file) : () => {}
|
||||
// handleUploadRefFile(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 삭제
|
||||
*/
|
||||
const handleFileDelete = () => {
|
||||
setRefImage(null)
|
||||
setCurrentCanvasPlan((prev) => ({ ...prev, bgFileName: null }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 주소 삭제
|
||||
*/
|
||||
const handleAddressDelete = () => {
|
||||
setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 주소로 구글 맵 이미지 다운로드
|
||||
*/
|
||||
const handleMapImageDown = async () => {
|
||||
if (queryRef.current.value === '' || queryRef.current.value === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const res = await get({ url: `http://localhost:3000/api/html2canvas?q=${queryRef.current.value}&fileNm=${uuidv4()}&zoom=20` })
|
||||
console.log('🚀 ~ handleMapImageDown ~ res:', res)
|
||||
setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: res.fileNm, mapPositionAddress: queryRef.current.value }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 플랜이 변경되면 플랜 상태 저장
|
||||
*/
|
||||
// useEffect(() => {
|
||||
// const handleCurrentPlan = async () => {
|
||||
// await promisePut({ url: '/api/canvas-management/canvas-statuses', data: currentCanvasPlan }).then((res) => {
|
||||
// console.log('🚀 ~ awaitpromisePut ~ res:', res)
|
||||
// })
|
||||
// }
|
||||
// handleCurrentPlan()
|
||||
// }, [currentCanvasPlan])
|
||||
|
||||
/**
|
||||
* RefFile이 캐드 도면 파일일 경우 변환하여 이미지로 저장
|
||||
* @param {*} file
|
||||
*/
|
||||
const handleUploadRefFile = async (file) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
await promisePost({ url: converterUrl, data: formData })
|
||||
.then((res) => {
|
||||
convertDwgToPng(res.data.Files[0].FileName, res.data.Files[0].FileData)
|
||||
swalFire({ text: '파일 변환 성공' })
|
||||
})
|
||||
.catch((err) => {
|
||||
swalFire({ text: '파일 변환 실패', icon: 'error' })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 라디오 버튼 컨트롤
|
||||
* @param {*} e
|
||||
*/
|
||||
const handleRefFileMethod = (e) => {
|
||||
setRefFileMethod(e.target.value)
|
||||
}
|
||||
|
||||
return {
|
||||
refImage,
|
||||
queryRef,
|
||||
setRefImage,
|
||||
handleRefFile,
|
||||
refFileMethod,
|
||||
setRefFileMethod,
|
||||
mapPositionAddress,
|
||||
setMapPositionAddress,
|
||||
handleRefFileMethod,
|
||||
handleFileDelete,
|
||||
handleAddressDelete,
|
||||
handleMapImageDown,
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
import { SessionContext } from '@/app/SessionProvider'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
|
||||
const reducer = (prevState, nextState) => {
|
||||
return { ...prevState, ...nextState }
|
||||
@ -21,35 +22,14 @@ const defaultEstimateData = {
|
||||
estimateType: 'YJOD', //주문분류
|
||||
remarks: '', //비고
|
||||
estimateOption: '', //견적특이사항
|
||||
// itemList: [{ id: 1, name: '' }],
|
||||
//아이템에 필요없는거 빼기
|
||||
itemList: [
|
||||
{
|
||||
amount: '',
|
||||
fileUploadFlg: '',
|
||||
itemChangeFlg: '',
|
||||
itemGroup: '',
|
||||
itemId: '', //키값??
|
||||
itemName: '',
|
||||
itemNo: '',
|
||||
moduleFlg: '',
|
||||
objectNo: '',
|
||||
pkgMaterialFlg: '',
|
||||
planNo: '',
|
||||
pnowW: '',
|
||||
salePrice: '',
|
||||
saleTotPrice: '',
|
||||
specification: '',
|
||||
unit: '',
|
||||
},
|
||||
],
|
||||
itemList: [],
|
||||
fileList: [],
|
||||
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
|
||||
priceCd: '',
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
// const updateItemInList = (itemList, id, updates) => {
|
||||
const updateItemInList = (itemList, itemId, updates) => {
|
||||
// return itemList.map((item) => (item.id === id ? { ...item, ...updates } : item))
|
||||
return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item))
|
||||
}
|
||||
|
||||
@ -59,6 +39,8 @@ export const useEstimateController = (planNo) => {
|
||||
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||
const [estimateData, setEstimateData] = useRecoilState(estimateState)
|
||||
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const { get, post, promisePost } = useAxios(globalLocaleState)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@ -87,81 +69,86 @@ export const useEstimateController = (planNo) => {
|
||||
}
|
||||
}
|
||||
|
||||
// const updateItem = (id, updates) => {
|
||||
const updateItem = (itemId, updates) => {
|
||||
setState({
|
||||
// itemList: updateItemInList(state.itemList, id, updates),
|
||||
itemList: updateItemInList(state.itemList, itemId, updates),
|
||||
})
|
||||
}
|
||||
|
||||
const addItem = () => {
|
||||
// const newId = Math.max(...state.itemList.map((item) => item.id)) + 1
|
||||
const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1
|
||||
const newItemDispOrder = (Math.max(...state.itemList.map((item) => item.dispOrder)) + 1) * 100
|
||||
setState({
|
||||
// itemList: [...state.itemList, { id: newId, name: '' }],
|
||||
//셋팅할필요없는거 빼기
|
||||
itemList: [
|
||||
...state.itemList,
|
||||
{
|
||||
itemId: newItemId,
|
||||
amount: '',
|
||||
fileUploadFlg: '',
|
||||
itemChangeFlg: '',
|
||||
itemGroup: '',
|
||||
dispOrder: newItemDispOrder,
|
||||
itemId: '', //제품번호
|
||||
itemNo: '', //형명
|
||||
itemName: '',
|
||||
itemNo: '',
|
||||
moduleFlg: '',
|
||||
objectNo: '',
|
||||
pkgMaterialFlg: '',
|
||||
planNo: '',
|
||||
pnowW: '',
|
||||
salePrice: '',
|
||||
saleTotPrice: '',
|
||||
specification: '',
|
||||
unit: '',
|
||||
amount: '', //수량
|
||||
unitPrice: '0',
|
||||
unit: '', //단위
|
||||
salePrice: '0', //단가
|
||||
saleTotPrice: '0', //금액(부가세별도)
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setEstimateData({ ...state, userId: session.userId })
|
||||
//sapSalesStoreCd 추가예정 필수값
|
||||
// setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd : session.sapSalesStoreCd })
|
||||
setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd: session.custCd })
|
||||
}, [state])
|
||||
|
||||
//견적서 저장
|
||||
const handleEstimateSubmit = async () => {
|
||||
//0. 필수체크
|
||||
let flag = true
|
||||
console.log('::담긴 estimateData:::', estimateData)
|
||||
//1. 첨부파일 저장
|
||||
const formData = new FormData()
|
||||
formData.append('file', estimateData.fileList)
|
||||
formData.append('objectNo', estimateData.objectNo)
|
||||
formData.append('planNo', estimateData.planNo)
|
||||
formData.append('category', '10')
|
||||
formData.append('userId', estimateData.userId)
|
||||
for (const value of formData.values()) {
|
||||
console.log('formData::', value)
|
||||
}
|
||||
|
||||
await promisePost({ url: '/api/file/fileUpload', data: formData }).then((res) => {
|
||||
console.log('파일저장::::::::::', res)
|
||||
})
|
||||
|
||||
//2. 상세데이터 저장
|
||||
|
||||
console.log('상세저장시작!!')
|
||||
return
|
||||
try {
|
||||
const result = await promisePost({
|
||||
url: ESTIMATE_API_ENDPOINT,
|
||||
data: estimateData,
|
||||
//아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼
|
||||
if (estimateData.itemList.length > 1) {
|
||||
estimateData.itemList.map((row) => {
|
||||
if (row.fileUploadFlg === '1') {
|
||||
if (estimateData.fileFlg === '0') {
|
||||
alert(getMessage('estimate.detail.save.requiredMsg'))
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
})
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Failed to submit estimate:', error)
|
||||
throw error
|
||||
}
|
||||
if (flag) {
|
||||
//1. 첨부파일 저장
|
||||
const formData = new FormData()
|
||||
formData.append('file', estimateData.fileList)
|
||||
formData.append('objectNo', estimateData.objectNo)
|
||||
formData.append('planNo', estimateData.planNo)
|
||||
formData.append('category', '10')
|
||||
formData.append('userId', estimateData.userId)
|
||||
// for (const value of formData.values()) {
|
||||
// console.log('formData::', value)
|
||||
// }
|
||||
|
||||
await post({ url: '/api/file/fileUpload', data: formData })
|
||||
|
||||
//2. 상세데이터 저장
|
||||
return
|
||||
await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => {
|
||||
if (res) {
|
||||
alert(getMessage('estimate.detail.save.alertMsg'))
|
||||
}
|
||||
})
|
||||
|
||||
// try {
|
||||
// const result = await promisePost({
|
||||
// url: ESTIMATE_API_ENDPOINT,
|
||||
// data: estimateData,
|
||||
// })
|
||||
// alert(getMessage('estimate.detail.save.alertMsg'))
|
||||
// return result
|
||||
// } catch (error) {
|
||||
// console.error('Failed to submit estimate:', error)
|
||||
// throw error
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -374,6 +374,9 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer))
|
||||
const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
|
||||
|
||||
console.log('trianglePolygon', trianglePolygon)
|
||||
console.log('selectedSurfacePolygon', selectedSurfacePolygon)
|
||||
|
||||
//지붕 밖으로 그렸을때
|
||||
if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) {
|
||||
swalFire({ text: '도머를 배치할 수 없습니다.', icon: 'error' })
|
||||
@ -496,6 +499,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
})
|
||||
canvas?.add(objectGroup)
|
||||
|
||||
objectGroup.getObjects().forEach((obj, index) => {
|
||||
console.log(`최초 pathOffset ${index}`, obj.get('pathOffset'))
|
||||
})
|
||||
|
||||
isDown = false
|
||||
initEvent()
|
||||
dbClickEvent()
|
||||
@ -917,20 +924,17 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
originY: 'center',
|
||||
left: changedCoords.x,
|
||||
top: changedCoords.y,
|
||||
width: width / 10,
|
||||
height: height / 10,
|
||||
})
|
||||
|
||||
if (target.name === 'roof') {
|
||||
//얘는 일단 도머에 적용함
|
||||
if (target.type === 'group') {
|
||||
target._objects.forEach((obj) => setSurfaceShapePattern(obj))
|
||||
} else {
|
||||
setSurfaceShapePattern(target)
|
||||
target.fire('modified')
|
||||
}
|
||||
//얘는 일단 도머에 적용함
|
||||
if (target.type === 'group') {
|
||||
target._objects.forEach((obj) => setSurfaceShapePattern(obj))
|
||||
}
|
||||
|
||||
// target.setCoords()
|
||||
canvas.renderAll()
|
||||
|
||||
if (target.type === 'group') reGroupObject(target)
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
|
||||
import { corridorDimensionSelector, settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
|
||||
import { setSurfaceShapePattern } from '@/util/canvas-util'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
|
||||
@ -17,6 +17,8 @@ export function useCanvasSetting() {
|
||||
const { option1, option2, dimensionDisplay } = settingModalFirstOptions
|
||||
const { option3, option4 } = settingModalSecondOptions
|
||||
|
||||
const corridorDimension = useRecoilValue(corridorDimensionSelector)
|
||||
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const { get, post } = useAxios(globalLocaleState)
|
||||
const { getMessage } = useMessage()
|
||||
@ -27,6 +29,36 @@ export function useCanvasSetting() {
|
||||
|
||||
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvas) {
|
||||
return
|
||||
}
|
||||
const { column } = corridorDimension
|
||||
const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText')
|
||||
switch (column) {
|
||||
case 'corridorDimension':
|
||||
lengthTexts.forEach((obj) => {
|
||||
if (obj.planeSize) {
|
||||
obj.set({ text: obj.planeSize.toString() })
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'realDimension':
|
||||
lengthTexts.forEach((obj) => {
|
||||
if (obj.actualSize) {
|
||||
obj.set({ text: obj.actualSize.toString() })
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'noneDimension':
|
||||
lengthTexts.forEach((obj) => {
|
||||
obj.set({ text: '' })
|
||||
})
|
||||
break
|
||||
}
|
||||
canvas.renderAll()
|
||||
}, [corridorDimension])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('useCanvasSetting useEffect 실행1')
|
||||
fetchSettings()
|
||||
@ -257,7 +289,7 @@ export function useCanvasSetting() {
|
||||
optionName = ['7']
|
||||
break
|
||||
case 'flowDisplay': //흐름방향 표시
|
||||
optionName = ['arrow']
|
||||
optionName = ['arrow', 'flowText']
|
||||
break
|
||||
case 'trestleDisplay': //가대 표시
|
||||
optionName = ['8']
|
||||
|
||||
@ -815,7 +815,7 @@ export function useAuxiliaryDrawing(id) {
|
||||
roofBase.lines.some((line) => isPointOnLine(line, { x: line.x2, y: line.y2 }))
|
||||
|
||||
if (inPolygon1 && inPolygon2) {
|
||||
line.attributes = { ...line.attributes, roofId: roofBase.id }
|
||||
line.attributes = { ...line.attributes, roofId: roofBase.id, actualSize: 0, planeSize: line.getLength() }
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { canvasState } from '@/store/canvasAtom'
|
||||
import { canvasState, currentObjectState } from '@/store/canvasAtom'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { setSurfaceShapePattern } from '@/util/canvas-util'
|
||||
import { splitPolygonWithLines } from '@/util/qpolygon-utils'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { roofDisplaySelector } from '@/store/settingAtom'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import ActualSizeSetting from '@/components/floor-plan/modal/roofAllocation/ActualSizeSetting'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
|
||||
// 지붕면 할당
|
||||
export function useRoofAllocationSetting(id) {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const roofDisplay = useRecoilValue(roofDisplaySelector)
|
||||
const { drawDirectionArrow } = usePolygon()
|
||||
const { closePopup } = usePopup()
|
||||
|
||||
const { drawDirectionArrow, addLengthText, splitPolygonWithLines } = usePolygon()
|
||||
const [popupId, setPopupId] = useState(uuidv4())
|
||||
const { addPopup, closePopup, closeAll } = usePopup()
|
||||
const { getMessage } = useMessage()
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const { swalFire } = useSwal()
|
||||
|
||||
const roofMaterials = [
|
||||
{
|
||||
id: 'A',
|
||||
@ -78,21 +81,47 @@ export function useRoofAllocationSetting(id) {
|
||||
])
|
||||
|
||||
const [radioValue, setRadioValue] = useState('A')
|
||||
const [editingLines, setEditingLines] = useState([])
|
||||
|
||||
const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0])
|
||||
|
||||
useEffect(() => {
|
||||
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) // roofPolygon.innerLines
|
||||
|
||||
roofBases.forEach((roof) => {
|
||||
roof.innerLines.forEach((line) => {
|
||||
if (!line.attributes.actualSize || line.attributes?.actualSize === 0) {
|
||||
line.set({
|
||||
strokeWidth: 4,
|
||||
stroke: 'black',
|
||||
selectable: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (editingLines.includes(line)) {
|
||||
line.set({
|
||||
strokeWidth: 2,
|
||||
stroke: 'black',
|
||||
selectable: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
if (currentObject && currentObject.name && !currentObject.name.toLowerCase().includes('text')) {
|
||||
currentObject.set({
|
||||
strokeWidth: 4,
|
||||
stroke: '#EA10AC',
|
||||
})
|
||||
}
|
||||
}, [currentObject])
|
||||
|
||||
useEffect(() => {
|
||||
// canvas.getObjects().filter((obj) => obj.type === 'QLine')
|
||||
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
if (roofBases.length === 0) {
|
||||
swalFire({ text: '할당할 지붕이 없습니다.' })
|
||||
closePopup(id)
|
||||
}
|
||||
// if (type === POLYGON_TYPE.ROOF) {
|
||||
// // 지붕면 할당
|
||||
//
|
||||
// } else if ('roof') {
|
||||
// // 지붕재 변경
|
||||
// }
|
||||
}, [])
|
||||
|
||||
const onAddRoofMaterial = () => {
|
||||
@ -105,6 +134,48 @@ export function useRoofAllocationSetting(id) {
|
||||
|
||||
// 선택한 지붕재로 할당
|
||||
const handleSave = () => {
|
||||
// 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정
|
||||
if (checkInnerLines()) {
|
||||
addPopup(popupId, 1, <ActualSizeSetting id={popupId} />)
|
||||
} else {
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
const handleAlloc = () => {
|
||||
if (!checkInnerLines()) {
|
||||
apply()
|
||||
} else {
|
||||
swalFire({
|
||||
type: 'alert',
|
||||
icon: 'error',
|
||||
text: getMessage('실제치수를 입력해 주세요.'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const checkInnerLines = () => {
|
||||
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) // roofPolygon.innerLines
|
||||
let result = false
|
||||
|
||||
roofBases.forEach((roof) => {
|
||||
roof.innerLines.forEach((line) => {
|
||||
if (!line.attributes.actualSize || line.attributes?.actualSize === 0) {
|
||||
line.set({
|
||||
strokeWidth: 4,
|
||||
stroke: 'black',
|
||||
selectable: true,
|
||||
})
|
||||
result = true
|
||||
}
|
||||
})
|
||||
})
|
||||
if (result) canvas?.renderAll()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const apply = () => {
|
||||
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
|
||||
roofBases.forEach((roofBase) => {
|
||||
@ -136,8 +207,26 @@ export function useRoofAllocationSetting(id) {
|
||||
removeTargets.forEach((obj) => {
|
||||
canvas.remove(obj)
|
||||
})
|
||||
setEditingLines([])
|
||||
closeAll()
|
||||
}
|
||||
|
||||
closePopup(id)
|
||||
const setLineSize = (id, size) => {
|
||||
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
roofBases.forEach((roof) => {
|
||||
roof.innerLines.forEach((line) => {
|
||||
if (id === line.id) {
|
||||
setEditingLines([...editingLines.filter((editLine) => editLine.id !== line.id), line])
|
||||
line.attributes.actualSize = size
|
||||
line.set({
|
||||
strokeWidth: 2,
|
||||
stroke: 'black',
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
canvas?.renderAll()
|
||||
}
|
||||
|
||||
const handleRadioOnChange = (e) => {
|
||||
@ -149,6 +238,8 @@ export function useRoofAllocationSetting(id) {
|
||||
onAddRoofMaterial,
|
||||
onDeleteRoofMaterial,
|
||||
handleRadioOnChange,
|
||||
handleAlloc,
|
||||
setLineSize,
|
||||
widths,
|
||||
lengths,
|
||||
rafters,
|
||||
|
||||
@ -167,9 +167,8 @@ export function useRoofShapeSetting(id) {
|
||||
]
|
||||
|
||||
const handleSave = () => {
|
||||
//기존 wallLine 삭제
|
||||
|
||||
let outerLines
|
||||
let direction
|
||||
|
||||
switch (shapeNum) {
|
||||
case 1: {
|
||||
@ -196,6 +195,7 @@ export function useRoofShapeSetting(id) {
|
||||
// 서쪽
|
||||
initLineSetting()
|
||||
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
direction = 'west'
|
||||
|
||||
outerLines.forEach((line) => {
|
||||
setWestAndEastRoof(line)
|
||||
@ -240,6 +240,7 @@ export function useRoofShapeSetting(id) {
|
||||
case 6: {
|
||||
initLineSetting()
|
||||
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
direction = 'east'
|
||||
|
||||
outerLines.forEach((line) => {
|
||||
setWestAndEastRoof(line)
|
||||
@ -285,6 +286,7 @@ export function useRoofShapeSetting(id) {
|
||||
case 7: {
|
||||
initLineSetting()
|
||||
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
direction = 'south'
|
||||
|
||||
outerLines.forEach((line) => {
|
||||
setSouthAndNorthRoof(line)
|
||||
@ -329,6 +331,7 @@ export function useRoofShapeSetting(id) {
|
||||
case 8: {
|
||||
initLineSetting()
|
||||
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
|
||||
direction = 'north'
|
||||
|
||||
outerLines.forEach((line) => {
|
||||
setSouthAndNorthRoof(line)
|
||||
@ -374,22 +377,22 @@ export function useRoofShapeSetting(id) {
|
||||
}
|
||||
|
||||
// 기존 wallLine, roofBase 제거
|
||||
canvas
|
||||
/*canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.name === POLYGON_TYPE.WALL)
|
||||
.forEach((line) => {
|
||||
canvas.remove(line)
|
||||
})
|
||||
})*/
|
||||
|
||||
canvas
|
||||
/*canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
.forEach((obj) => {
|
||||
canvas.remove(...obj.innerLines)
|
||||
canvas.remove(obj)
|
||||
})
|
||||
})*/
|
||||
|
||||
const polygon = addPolygonByLines(outerLines, { name: POLYGON_TYPE.WALL })
|
||||
const polygon = addPolygonByLines(outerLines, { name: POLYGON_TYPE.WALL, direction })
|
||||
polygon.lines = [...outerLines]
|
||||
|
||||
addPitchTextsByOuterLines()
|
||||
@ -397,7 +400,6 @@ export function useRoofShapeSetting(id) {
|
||||
|
||||
canvas?.renderAll()
|
||||
roof.drawHelpLine()
|
||||
// setShowRoofShapeSettingModal(false)
|
||||
isFixRef.current = true
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import { usePopup } from '@/hooks/usePopup'
|
||||
import { roofDisplaySelector } from '@/store/settingAtom'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { fontSelector } from '@/store/fontAtom'
|
||||
import { slopeSelector } from '@/store/commonAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
|
||||
export function useSurfaceShapeBatch() {
|
||||
const { getMessage } = useMessage()
|
||||
@ -22,6 +24,7 @@ export function useSurfaceShapeBatch() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const globalPitch = useRecoilValue(globalPitchState)
|
||||
const roofDisplay = useRecoilValue(roofDisplaySelector)
|
||||
const slope = useRecoilValue(slopeSelector(globalPitch))
|
||||
const { swalFire } = useSwal()
|
||||
const { addCanvasMouseEventListener, initEvent } = useEvent()
|
||||
const { closePopup } = usePopup()
|
||||
@ -126,6 +129,7 @@ export function useSurfaceShapeBatch() {
|
||||
addCanvasMouseEventListener('mouse:down', (e) => {
|
||||
isDrawing = false
|
||||
obj.set('name', POLYGON_TYPE.ROOF)
|
||||
obj.set('surfaceId', surfaceId)
|
||||
initEvent()
|
||||
setSurfaceShapePattern(obj, roofDisplay.column)
|
||||
closePopup(id)
|
||||
@ -580,18 +584,19 @@ export function useSurfaceShapeBatch() {
|
||||
text: '배치면 내용을 전부 삭제하시겠습니까?',
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
canvas?.getObjects().forEach((obj) => {
|
||||
if (
|
||||
obj.name === POLYGON_TYPE.ROOF ||
|
||||
obj.name === BATCH_TYPE.OPENING ||
|
||||
obj.name === BATCH_TYPE.SHADOW ||
|
||||
obj.name === BATCH_TYPE.TRIANGLE_DORMER ||
|
||||
obj.name === BATCH_TYPE.PENTAGON_DORMER ||
|
||||
obj.name === 'lengthText'
|
||||
) {
|
||||
canvas?.remove(obj)
|
||||
}
|
||||
})
|
||||
// canvas?.getObjects().forEach((obj) => {
|
||||
// if (
|
||||
// obj.name === POLYGON_TYPE.ROOF ||
|
||||
// obj.name === BATCH_TYPE.OPENING ||
|
||||
// obj.name === BATCH_TYPE.SHADOW ||
|
||||
// obj.name === BATCH_TYPE.TRIANGLE_DORMER ||
|
||||
// obj.name === BATCH_TYPE.PENTAGON_DORMER ||
|
||||
// obj.name === 'lengthText'
|
||||
// ) {
|
||||
// canvas?.remove(obj)
|
||||
// }
|
||||
// })
|
||||
canvas.clear()
|
||||
swalFire({ text: '삭제 완료 되었습니다.' })
|
||||
},
|
||||
// denyFn: () => {
|
||||
@ -657,7 +662,7 @@ export function useSurfaceShapeBatch() {
|
||||
}
|
||||
|
||||
function getAllRelatedObjects(id) {
|
||||
const result = []
|
||||
const ult = []
|
||||
const map = new Map()
|
||||
|
||||
// Create a map of objects by their id
|
||||
@ -688,52 +693,8 @@ export function useSurfaceShapeBatch() {
|
||||
const roof = canvas.getActiveObject()
|
||||
|
||||
if (roof) {
|
||||
let isDragging = false
|
||||
|
||||
const childrenObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id)
|
||||
|
||||
console.log('childrenObjects', childrenObjects)
|
||||
|
||||
// const groupObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id && obj.type === 'group')
|
||||
|
||||
// const ungroupObjects = [] // 그룹 해제된 객체들
|
||||
// const groupChildObjects = []
|
||||
|
||||
// groupObjects.forEach((obj) => {
|
||||
// obj._restoreObjectsState()
|
||||
// obj.getObjects().forEach((o) => {
|
||||
// o.set({
|
||||
// ungroupYn: true,
|
||||
// })
|
||||
// canvas.add(o)
|
||||
// ungroupObjects.push(o)
|
||||
// })
|
||||
// canvas.remove(obj)
|
||||
// })
|
||||
|
||||
// const childObjects = findAllChildren(roof.id)
|
||||
// groupObjects.forEach((obj) => {
|
||||
// groupChildObjects.push(...obj.getObjects())
|
||||
// })
|
||||
|
||||
// console.log('ungroupObjects', ungroupObjects)
|
||||
// console.log('childObjects', childObjects)
|
||||
// console.log('groupChildObjects', groupChildObjects)
|
||||
|
||||
// const children = canvas.getObjects().filter((obj) => obj.parentId === roof.id)
|
||||
// let grandChildren = []
|
||||
|
||||
// children.forEach((child) => {
|
||||
// if (child.type === 'group') {
|
||||
// child.getObjects().forEach((grandChild) => {
|
||||
// const groupObjects = canvas.getObjects().filter((obj) => obj.parentId === grandChild.id)
|
||||
// grandChildren.push(...groupObjects)
|
||||
// })
|
||||
// } else {
|
||||
// grandChildren.push(...canvas.getObjects().filter((obj) => obj.parentId === child.id))
|
||||
// }
|
||||
// })
|
||||
|
||||
const selectionArray = [roof, ...childrenObjects]
|
||||
|
||||
const selection = new fabric.ActiveSelection(selectionArray, {
|
||||
@ -748,7 +709,6 @@ export function useSurfaceShapeBatch() {
|
||||
canvas.setActiveObject(selection)
|
||||
|
||||
addCanvasMouseEventListener('mouse:up', (e) => {
|
||||
isDragging = false
|
||||
canvas.selection = true
|
||||
|
||||
canvas.discardActiveObject() // 모든 선택 해제
|
||||
@ -768,7 +728,6 @@ export function useSurfaceShapeBatch() {
|
||||
|
||||
canvas.renderAll()
|
||||
roof.fire('polygonMoved')
|
||||
if (roof.type === 'group') reGroupObject(obj)
|
||||
drawDirectionArrow(roof)
|
||||
initEvent()
|
||||
})
|
||||
@ -805,9 +764,223 @@ export function useSurfaceShapeBatch() {
|
||||
canvas?.remove(groupObj)
|
||||
}
|
||||
|
||||
const resizeSurfaceShapeBatch = (side, target, width, height) => {
|
||||
const objectWidth = target.width
|
||||
const objectHeight = target.height
|
||||
const changeWidth = width / 10 / objectWidth
|
||||
const changeHeight = height / 10 / objectHeight
|
||||
let sideX = 'left'
|
||||
let sideY = 'top'
|
||||
|
||||
//그룹 중심점 변경
|
||||
if (side === 2) {
|
||||
sideX = 'right'
|
||||
sideY = 'top'
|
||||
} else if (side === 3) {
|
||||
sideX = 'left'
|
||||
sideY = 'bottom'
|
||||
} else if (side === 4) {
|
||||
sideX = 'right'
|
||||
sideY = 'bottom'
|
||||
}
|
||||
|
||||
//변경 전 좌표
|
||||
const newCoords = target.getPointByOrigin(sideX, sideY)
|
||||
|
||||
target.set({
|
||||
originX: sideX,
|
||||
originY: sideY,
|
||||
left: newCoords.x,
|
||||
top: newCoords.y,
|
||||
})
|
||||
|
||||
target.scaleX = changeWidth
|
||||
target.scaleY = changeHeight
|
||||
|
||||
const currentPoints = target.getCurrentPoints()
|
||||
|
||||
target.set({
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
width: parseInt((width / 10).toFixed(0)),
|
||||
height: parseInt((height / 10).toFixed(0)),
|
||||
})
|
||||
//크기 변경후 좌표를 재 적용
|
||||
const changedCoords = target.getPointByOrigin('center', 'center')
|
||||
|
||||
target.set({
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
left: changedCoords.x,
|
||||
top: changedCoords.y,
|
||||
})
|
||||
|
||||
//면형상 리사이즈시에만
|
||||
target.fire('polygonMoved')
|
||||
target.points = currentPoints
|
||||
target.fire('modified')
|
||||
|
||||
setSurfaceShapePattern(target, roofDisplay.column)
|
||||
if (target.direction) {
|
||||
drawDirectionArrow(target)
|
||||
}
|
||||
target.setCoords()
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
const surfaceShapeActualSize = () => {
|
||||
const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof')
|
||||
let notParallelLines = []
|
||||
|
||||
const roofArray = []
|
||||
|
||||
roofs.forEach((obj) => {
|
||||
const roof = {
|
||||
id: obj.id,
|
||||
surfaceId: obj.surfaceId,
|
||||
targetLines: [],
|
||||
baseLines: [],
|
||||
direction: obj.direction,
|
||||
}
|
||||
obj.lines.forEach((line) => {
|
||||
if (obj.direction === 'north' || obj.direction === 'south') {
|
||||
if (line.y1 !== line.y2) {
|
||||
roof.targetLines.push(line)
|
||||
} else {
|
||||
roof.baseLines.push(line)
|
||||
}
|
||||
} else {
|
||||
if (line.x1 !== line.x2) {
|
||||
roof.targetLines.push(line)
|
||||
} else {
|
||||
roof.baseLines.push(line)
|
||||
}
|
||||
}
|
||||
})
|
||||
roofArray.push(roof)
|
||||
})
|
||||
|
||||
const slopeRadians = slope.angleValue * (Math.PI / 180)
|
||||
const tanSlope = Math.tan(slopeRadians)
|
||||
|
||||
roofArray.forEach((roof) => {
|
||||
roof.targetLines.forEach((line) => {
|
||||
let calcLength = line.length
|
||||
|
||||
if (roof.surfaceId === 1) {
|
||||
calcLength = roof.direction === 'north' || roof.direction === 'south' ? Math.abs(line.y1 - line.y2) : Math.abs(line.x1 - line.x2)
|
||||
}
|
||||
|
||||
const h = calcLength * tanSlope
|
||||
const resultLength = Math.sqrt(Math.pow(calcLength, 2) + Math.pow(h, 2))
|
||||
|
||||
line.set({
|
||||
attributes: {
|
||||
...line.attributes,
|
||||
actualSize: parseInt((Math.floor(resultLength) * 10).toFixed(0)),
|
||||
planeSize: parseInt(line.length * 10),
|
||||
},
|
||||
})
|
||||
})
|
||||
roof.baseLines.forEach((line) => {
|
||||
line.set({
|
||||
attributes: {
|
||||
...line.attributes,
|
||||
actualSize: parseInt((Math.floor(line.length) * 10).toFixed(0)),
|
||||
planeSize: parseInt(line.length * 10),
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const changeSurfaceLinePropertyEvent = (roof) => {
|
||||
let tmpLines = []
|
||||
roof.set({
|
||||
selectable: false,
|
||||
})
|
||||
canvas.discardActiveObject()
|
||||
roof.lines.forEach((obj, index) => {
|
||||
const tmpLine = new QLine([obj.x1, obj.y1, obj.x2, obj.y2], {
|
||||
...obj,
|
||||
stroke: 'rgb(3, 255, 0)',
|
||||
strokeWidth: 8,
|
||||
selectable: true,
|
||||
name: 'lineProperty',
|
||||
index: index,
|
||||
})
|
||||
|
||||
tmpLines.push(tmpLine)
|
||||
canvas.add(tmpLine)
|
||||
})
|
||||
|
||||
addCanvasMouseEventListener('mouse:down', (e) => {
|
||||
const selectedLine = e.target
|
||||
if (selectedLine) {
|
||||
selectedLine.set({
|
||||
stroke: 'red',
|
||||
name: 'selectedLineProperty',
|
||||
})
|
||||
tmpLines.forEach((line) => {
|
||||
if (line.index !== selectedLine.index) {
|
||||
line.set({
|
||||
stroke: 'rgb(3, 255, 0)',
|
||||
name: 'lineProperty',
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
tmpLines.forEach((line) => {
|
||||
line.set({
|
||||
stroke: 'rgb(3, 255, 0)',
|
||||
name: 'lineProperty',
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
const changeSurfaceLineProperty = (property) => {
|
||||
console.log(property)
|
||||
if (!property) {
|
||||
swalFire({ text: getMessage('modal.line.property.change'), icon: 'error' })
|
||||
return
|
||||
}
|
||||
|
||||
const selectedLine = canvas.getActiveObjects()
|
||||
if (selectedLine && selectedLine[0].name === 'selectedLineProperty') {
|
||||
swalFire({
|
||||
text: getMessage('modal.line.property.change.confirm'),
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
selectedLine.set({
|
||||
type: property.value,
|
||||
})
|
||||
},
|
||||
})
|
||||
} else {
|
||||
swalFire({ text: getMessage('modal.line.property.change.unselect'), icon: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
const changeSurfaceFlowDirection = (roof, direction, orientation) => {
|
||||
roof.set({
|
||||
direction: direction,
|
||||
})
|
||||
drawDirectionArrow(roof)
|
||||
canvas?.renderAll()
|
||||
}
|
||||
|
||||
return {
|
||||
applySurfaceShape,
|
||||
deleteAllSurfacesAndObjects,
|
||||
moveSurfaceShapeBatch,
|
||||
resizeSurfaceShapeBatch,
|
||||
surfaceShapeActualSize,
|
||||
changeSurfaceFlowDirection,
|
||||
changeSurfaceLinePropertyEvent,
|
||||
changeSurfaceLineProperty,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, 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 } from '@/store/canvasAtom'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import { usePlan } from '@/hooks/usePlan'
|
||||
import { fontSelector } from '@/store/fontAtom'
|
||||
@ -12,8 +12,6 @@ export function useCanvasEvent() {
|
||||
const [canvasForEvent, setCanvasForEvent] = useState(null)
|
||||
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
|
||||
const canvasSize = useRecoilValue(canvasSizeState)
|
||||
const fontSize = useRecoilValue(fontSizeState)
|
||||
const fontFamily = useRecoilValue(fontFamilyState)
|
||||
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
|
||||
const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
|
||||
const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan()
|
||||
@ -211,7 +209,7 @@ export function useCanvasEvent() {
|
||||
setCurrentObject(target)
|
||||
const { selected } = e
|
||||
|
||||
if (selected.length > 0) {
|
||||
if (selected?.length > 0) {
|
||||
selected.forEach((obj) => {
|
||||
if (obj.type === 'QPolygon') {
|
||||
obj.set({ stroke: 'red' })
|
||||
@ -224,7 +222,7 @@ export function useCanvasEvent() {
|
||||
setCurrentObject(null)
|
||||
const { deselected } = e
|
||||
|
||||
if (deselected.length > 0) {
|
||||
if (deselected?.length > 0) {
|
||||
deselected.forEach((obj) => {
|
||||
if (obj.type === 'QPolygon') {
|
||||
obj.set({ stroke: 'black' })
|
||||
@ -238,7 +236,7 @@ export function useCanvasEvent() {
|
||||
setCurrentObject(target)
|
||||
const { selected, deselected } = e
|
||||
|
||||
if (deselected.length > 0) {
|
||||
if (deselected?.length > 0) {
|
||||
deselected.forEach((obj) => {
|
||||
if (obj.type === 'QPolygon') {
|
||||
obj.set({ stroke: 'black' })
|
||||
@ -246,7 +244,7 @@ export function useCanvasEvent() {
|
||||
})
|
||||
}
|
||||
|
||||
if (selected.length > 0) {
|
||||
if (selected?.length > 0) {
|
||||
selected.forEach((obj) => {
|
||||
if (obj.type === 'QPolygon') {
|
||||
obj.set({ stroke: 'red' })
|
||||
|
||||
@ -51,7 +51,7 @@ export function useContextMenu() {
|
||||
const [column, setColumn] = useState(null)
|
||||
const { handleZoomClear } = useCanvasEvent()
|
||||
const { moveObjectBatch } = useObjectBatch({})
|
||||
const { moveSurfaceShapeBatch } = useSurfaceShapeBatch()
|
||||
const { moveSurfaceShapeBatch, surfaceShapeActualSize } = useSurfaceShapeBatch()
|
||||
const currentMenuSetting = () => {
|
||||
switch (currentMenu) {
|
||||
case MENU.PLAN_DRAWING:
|
||||
@ -176,6 +176,7 @@ export function useContextMenu() {
|
||||
{
|
||||
id: 'sizeEdit',
|
||||
name: getMessage('contextmenu.size.edit'),
|
||||
component: <SizeSetting id={popupId} target={currentObject} />,
|
||||
},
|
||||
{
|
||||
id: 'remove',
|
||||
@ -211,6 +212,7 @@ export function useContextMenu() {
|
||||
{
|
||||
id: 'flowDirectionEdit',
|
||||
name: getMessage('contextmenu.flow.direction.edit'),
|
||||
component: <FlowDirectionSetting id={popupId} target={currentObject} />,
|
||||
},
|
||||
],
|
||||
])
|
||||
@ -257,7 +259,6 @@ export function useContextMenu() {
|
||||
}, [currentContextMenu])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('currentObject', currentObject)
|
||||
if (currentObject?.name) {
|
||||
switch (currentObject.name) {
|
||||
case 'triangleDormer':
|
||||
@ -303,6 +304,11 @@ export function useContextMenu() {
|
||||
case 'roof':
|
||||
setContextMenu([
|
||||
[
|
||||
{
|
||||
id: 'surfaceShapeActualSize',
|
||||
name: '면형상 실측',
|
||||
fn: () => surfaceShapeActualSize(),
|
||||
},
|
||||
{
|
||||
id: 'sizeEdit',
|
||||
name: '사이즈 변경',
|
||||
@ -336,7 +342,7 @@ export function useContextMenu() {
|
||||
{
|
||||
id: 'linePropertyEdit',
|
||||
name: getMessage('contextmenu.line.property.edit'),
|
||||
component: <LinePropertySetting id={popupId} />,
|
||||
component: <LinePropertySetting id={popupId} target={currentObject} />,
|
||||
},
|
||||
{
|
||||
id: 'flowDirectionEdit',
|
||||
|
||||
@ -1515,9 +1515,17 @@ export function useMode() {
|
||||
pitch: 4,
|
||||
sleeve: true,
|
||||
}
|
||||
/*if (index === 1 || index === 3) {
|
||||
/*if (index === 1) {
|
||||
line.attributes = {
|
||||
type: LINE_TYPE.WALLLINE.WALL,
|
||||
type: LINE_TYPE.WALLLINE.SHED,
|
||||
offset: 30, //출폭
|
||||
width: 30, //폭
|
||||
pitch: 4, //구배
|
||||
sleeve: true, //소매
|
||||
}
|
||||
} else if (index === 5 || index === 3) {
|
||||
line.attributes = {
|
||||
type: LINE_TYPE.WALLLINE.EAVES,
|
||||
offset: 50, //출폭
|
||||
width: 30, //폭
|
||||
pitch: 4, //구배
|
||||
@ -1525,8 +1533,8 @@ export function useMode() {
|
||||
}
|
||||
} else {
|
||||
line.attributes = {
|
||||
type: LINE_TYPE.WALLLINE.EAVES,
|
||||
offset: 40,
|
||||
type: LINE_TYPE.WALLLINE.GABLE,
|
||||
offset: 20,
|
||||
width: 50,
|
||||
pitch: 4,
|
||||
sleeve: true,
|
||||
@ -1746,12 +1754,20 @@ export function useMode() {
|
||||
return { x1: point.x, y1: point.y }
|
||||
}),
|
||||
)
|
||||
if (wall.direction) {
|
||||
roof.direction = wall.direction
|
||||
}
|
||||
roof.name = POLYGON_TYPE.ROOF
|
||||
roof.setWall(wall)
|
||||
|
||||
roof.lines.forEach((line, index) => {
|
||||
const lineLength = Math.sqrt(
|
||||
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
|
||||
)
|
||||
line.attributes = {
|
||||
roofId: roof.id,
|
||||
planeSize: lineLength,
|
||||
actualSize: lineLength,
|
||||
wallLine: wall.lines[index].id,
|
||||
type: wall.lines[index].attributes.type,
|
||||
offset: wall.lines[index].attributes.offset,
|
||||
@ -1766,13 +1782,15 @@ export function useMode() {
|
||||
}
|
||||
|
||||
wall.lines.forEach((line, index) => {
|
||||
const lineLength = Math.sqrt(
|
||||
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
|
||||
)
|
||||
line.attributes.roofId = roof.id
|
||||
line.attributes.currentRoof = roof.lines[index].id
|
||||
line.attributes.planeSize = lineLength
|
||||
line.attributes.actualSize = lineLength
|
||||
})
|
||||
|
||||
console.log('drawRoofPolygon roof : ', roof)
|
||||
console.log('drawRoofPolygon wall : ', wall)
|
||||
|
||||
setRoof(roof)
|
||||
setWall(wall)
|
||||
|
||||
|
||||
@ -187,6 +187,8 @@ export function usePlan() {
|
||||
userId: item.userId,
|
||||
canvasStatus: dbToCanvasFormat(item.canvasStatus),
|
||||
isCurrent: false,
|
||||
bgImageName: item.bgImageName,
|
||||
mapPositionAddress: item.mapPositionAddress,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { fabric } from 'fabric'
|
||||
import { getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util'
|
||||
import { getDegreeByChon, getDirectionByPoint, isPointOnLine } from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import { isSamePoint } from '@/util/qpolygon-utils'
|
||||
import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils'
|
||||
import { flowDisplaySelector } from '@/store/settingAtom'
|
||||
import { fontSelector } from '@/store/fontAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
|
||||
export const usePolygon = () => {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -25,6 +27,7 @@ export const usePolygon = () => {
|
||||
})
|
||||
|
||||
canvas?.add(polygon)
|
||||
addLengthText(polygon)
|
||||
|
||||
return polygon
|
||||
}
|
||||
@ -40,7 +43,64 @@ export const usePolygon = () => {
|
||||
}
|
||||
|
||||
const addLengthText = (polygon) => {
|
||||
const points = polygon.get('points')
|
||||
const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText' && obj.parentId === polygon.id)
|
||||
lengthTexts.forEach((text) => {
|
||||
canvas.remove(text)
|
||||
})
|
||||
const lines = polygon.lines
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
const length = line.getLength()
|
||||
const { planeSize, actualSize } = line.attributes
|
||||
const scaleX = line.scaleX
|
||||
const scaleY = line.scaleY
|
||||
const x1 = line.left
|
||||
const y1 = line.top
|
||||
const x2 = line.left + line.width * scaleX
|
||||
const y2 = line.top + line.height * scaleY
|
||||
|
||||
let left, top
|
||||
|
||||
if (line.direction === 'left' || line.direction === 'right') {
|
||||
left = (x1 + x2) / 2
|
||||
top = (y1 + y2) / 2 + 10
|
||||
} else if (line.direction === 'top' || line.direction === 'bottom') {
|
||||
left = (x1 + x2) / 2 + 10
|
||||
top = (y1 + y2) / 2
|
||||
}
|
||||
|
||||
const minX = line.left
|
||||
const maxX = line.left + line.width
|
||||
const minY = line.top
|
||||
const maxY = line.top + line.length
|
||||
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
|
||||
|
||||
const text = new fabric.Textbox(planeSize ? planeSize.toString() : length.toString(), {
|
||||
left: left,
|
||||
top: top,
|
||||
fontSize: lengthTextFontOptions.fontSize.value,
|
||||
minX,
|
||||
maxX,
|
||||
minY,
|
||||
maxY,
|
||||
parentDirection: line.direction,
|
||||
parentDegree: degree,
|
||||
parentId: polygon.id,
|
||||
planeSize,
|
||||
actualSize,
|
||||
editable: false,
|
||||
selectable: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
parent: polygon,
|
||||
name: 'lengthText',
|
||||
})
|
||||
|
||||
canvas.add(text)
|
||||
})
|
||||
|
||||
/*const points = polygon.get('points')
|
||||
points.forEach((start, i) => {
|
||||
const end = points[(i + 1) % points.length]
|
||||
const dx = end.x - start.x
|
||||
@ -71,12 +131,12 @@ export const usePolygon = () => {
|
||||
lockScalingY: true,
|
||||
idx: i,
|
||||
name: 'lengthText',
|
||||
parent: this,
|
||||
parent: polygon,
|
||||
})
|
||||
|
||||
// this.texts.push(text)
|
||||
canvas.add(text)
|
||||
})
|
||||
})*/
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
@ -409,7 +469,8 @@ export const usePolygon = () => {
|
||||
|
||||
const addTextByArrows = (arrows, txt, canvas) => {
|
||||
arrows.forEach((arrow, index) => {
|
||||
const textStr = `${txt}${index + 1} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
|
||||
// const textStr = `${txt}${index + 1} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
|
||||
const textStr = `${txt} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
|
||||
|
||||
const text = new fabric.Text(`${textStr}`, {
|
||||
fontSize: flowFontOptions.fontSize.value,
|
||||
@ -432,10 +493,271 @@ export const usePolygon = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const splitPolygonWithLines = (polygon) => {
|
||||
polygon.set({ visible: false })
|
||||
let innerLines = [...polygon.innerLines]
|
||||
let polygonLines = [...polygon.lines]
|
||||
const roofs = []
|
||||
|
||||
let delIndexs = []
|
||||
let newLines = []
|
||||
|
||||
polygonLines.forEach((line, index) => {
|
||||
line.tempIndex = index
|
||||
innerLines.forEach((innerLine) => {
|
||||
let newLine1, newLine2
|
||||
if (isPointOnLine(line, innerLine.startPoint)) {
|
||||
// 해당 line을 startPoint로 나눈 line2개를 canvas에 추가 하고 기존 line을 제거한다.
|
||||
newLine1 = new QLine([line.x1, line.y1, innerLine.startPoint.x, innerLine.startPoint.y], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
|
||||
newLine2 = new QLine([innerLine.startPoint.x, innerLine.startPoint.y, line.x2, line.y2], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
delIndexs.push(polygonLines.indexOf(line))
|
||||
canvas.remove(polygonLines[polygonLines.indexOf(line)])
|
||||
if (newLine1.length / 10 > 10) {
|
||||
newLines.push(newLine1)
|
||||
}
|
||||
if (newLine2.length / 10 > 10) {
|
||||
newLines.push(newLine2)
|
||||
}
|
||||
}
|
||||
if (isPointOnLine(line, innerLine.endPoint)) {
|
||||
newLine1 = new QLine([line.x1, line.y1, innerLine.endPoint.x, innerLine.endPoint.y], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
|
||||
newLine2 = new QLine([innerLine.endPoint.x, innerLine.endPoint.y, line.x2, line.y2], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
delIndexs.push(polygonLines.indexOf(line))
|
||||
canvas.remove(polygonLines[polygonLines.indexOf(line)])
|
||||
if (newLine1.length / 10 > 10) {
|
||||
newLines.push(newLine1)
|
||||
}
|
||||
if (newLine2.length / 10 > 10) {
|
||||
newLines.push(newLine2)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
polygonLines = polygonLines.filter((line) => !delIndexs.includes(line.tempIndex))
|
||||
polygonLines = [...polygonLines, ...newLines]
|
||||
|
||||
const allLines = [...polygonLines, ...innerLines]
|
||||
|
||||
/**
|
||||
* 왼쪽 상단을 startPoint로 전부 변경
|
||||
*/
|
||||
allLines.forEach((line) => {
|
||||
let startPoint // 시작점
|
||||
let endPoint // 끝점
|
||||
if (line.x1 < line.x2) {
|
||||
startPoint = { x: line.x1, y: line.y1 }
|
||||
endPoint = { x: line.x2, y: line.y2 }
|
||||
} else if (line.x1 > line.x2) {
|
||||
startPoint = { x: line.x2, y: line.y2 }
|
||||
endPoint = { x: line.x1, y: line.y1 }
|
||||
} else {
|
||||
if (line.y1 < line.y2) {
|
||||
startPoint = { x: line.x1, y: line.y1 }
|
||||
endPoint = { x: line.x2, y: line.y2 }
|
||||
} else {
|
||||
startPoint = { x: line.x2, y: line.y2 }
|
||||
endPoint = { x: line.x1, y: line.y1 }
|
||||
}
|
||||
}
|
||||
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = endPoint
|
||||
})
|
||||
|
||||
polygonLines.forEach((line) => {
|
||||
const startPoint = line.startPoint // 시작점
|
||||
let arrivalPoint = line.endPoint // 도착점
|
||||
|
||||
let currentPoint = startPoint
|
||||
const roofPoints = [startPoint]
|
||||
|
||||
const startLine = line
|
||||
const visitPoints = [startPoint]
|
||||
const visitLines = [startLine]
|
||||
let cnt = 0
|
||||
|
||||
while (!isSamePoint(currentPoint, arrivalPoint)) {
|
||||
let nextLines = allLines.filter(
|
||||
(line2) =>
|
||||
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
|
||||
line !== line2 &&
|
||||
innerLines.includes(line2) &&
|
||||
!visitLines.includes(line2),
|
||||
)
|
||||
|
||||
if (nextLines.length === 0) {
|
||||
nextLines = allLines.filter(
|
||||
(line2) =>
|
||||
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
|
||||
line !== line2 &&
|
||||
!visitLines.includes(line2),
|
||||
)
|
||||
}
|
||||
|
||||
if (!nextLines) {
|
||||
break
|
||||
}
|
||||
|
||||
let comparisonPoints = []
|
||||
|
||||
nextLines.forEach((nextLine) => {
|
||||
if (isSamePoint(nextLine.startPoint, currentPoint)) {
|
||||
comparisonPoints.push(nextLine.endPoint)
|
||||
} else {
|
||||
comparisonPoints.push(nextLine.startPoint)
|
||||
}
|
||||
})
|
||||
|
||||
comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point)))
|
||||
comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint))
|
||||
|
||||
const minDistancePoint = comparisonPoints.reduce((prev, current) => {
|
||||
const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2))
|
||||
const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2))
|
||||
|
||||
return prevDistance < currentDistance ? prev : current
|
||||
}, comparisonPoints[0])
|
||||
|
||||
nextLines.forEach((nextLine) => {
|
||||
if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) {
|
||||
visitLines.push(nextLine)
|
||||
}
|
||||
})
|
||||
|
||||
currentPoint = { ...minDistancePoint }
|
||||
roofPoints.push(currentPoint)
|
||||
cnt++
|
||||
if (cnt > 100) {
|
||||
throw new Error('무한루프')
|
||||
}
|
||||
}
|
||||
roofs.push(roofPoints)
|
||||
})
|
||||
|
||||
const newRoofs = removeDuplicatePolygons(roofs)
|
||||
|
||||
newRoofs.forEach((roofPoint, index) => {
|
||||
let defense, pitch
|
||||
const polygonLines = [...polygon.lines]
|
||||
|
||||
let representLines = []
|
||||
let representLine
|
||||
|
||||
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
|
||||
polygonLines.forEach((line) => {
|
||||
let startFlag = false
|
||||
let endFlag = false
|
||||
const startPoint = line.startPoint
|
||||
const endPoint = line.endPoint
|
||||
roofPoint.forEach((point, index) => {
|
||||
if (isSamePoint(point, startPoint)) {
|
||||
startFlag = true
|
||||
}
|
||||
if (isSamePoint(point, endPoint)) {
|
||||
endFlag = true
|
||||
}
|
||||
})
|
||||
|
||||
if (startFlag && endFlag) {
|
||||
if (!representLines.includes(line)) {
|
||||
representLines.push(line)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// representLines중 가장 긴 line을 찾는다.
|
||||
representLines.forEach((line) => {
|
||||
if (!representLine) {
|
||||
representLine = line
|
||||
} else {
|
||||
if (representLine.length < line.length) {
|
||||
representLine = line
|
||||
}
|
||||
}
|
||||
})
|
||||
const direction = polygon.direction ?? representLine.direction
|
||||
const polygonDirection = polygon.direction
|
||||
|
||||
switch (direction) {
|
||||
case 'top':
|
||||
defense = 'east'
|
||||
break
|
||||
case 'right':
|
||||
defense = 'south'
|
||||
break
|
||||
case 'bottom':
|
||||
defense = 'west'
|
||||
break
|
||||
case 'left':
|
||||
defense = 'north'
|
||||
break
|
||||
}
|
||||
pitch = polygon.lines[index].attributes?.pitch ?? 0
|
||||
|
||||
const roof = new QPolygon(roofPoint, {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
fill: 'transparent',
|
||||
strokeWidth: 3,
|
||||
name: POLYGON_TYPE.ROOF,
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
selectable: true,
|
||||
defense: defense,
|
||||
direction: polygonDirection ?? defense,
|
||||
pitch: pitch,
|
||||
})
|
||||
|
||||
//allLines중 생성된 roof와 관련있는 line을 찾는다.
|
||||
|
||||
roof.lines = [...polygon.lines, ...polygon.innerLines].filter((line) => {
|
||||
let startFlag = false
|
||||
let endFlag = false
|
||||
const startPoint = line.startPoint
|
||||
const endPoint = line.endPoint
|
||||
roofPoint.forEach((point, index) => {
|
||||
if (isSamePoint(point, startPoint)) {
|
||||
startFlag = true
|
||||
}
|
||||
if (isSamePoint(point, endPoint)) {
|
||||
endFlag = true
|
||||
}
|
||||
})
|
||||
|
||||
return startFlag && endFlag
|
||||
})
|
||||
|
||||
canvas.add(roof)
|
||||
addLengthText(roof)
|
||||
canvas.remove(polygon)
|
||||
canvas.renderAll()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
addPolygon,
|
||||
addPolygonByLines,
|
||||
removePolygon,
|
||||
drawDirectionArrow,
|
||||
addLengthText,
|
||||
splitPolygonWithLines,
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,6 +285,12 @@
|
||||
"modal.flow.direction.setting": "流れ方向の設定",
|
||||
"modal.flow.direction.setting.info": "流れ方向を選択してください。",
|
||||
"modal.image.size.setting": "画像のサイズ変更",
|
||||
"modal.actual.size.setting": "実測値設定",
|
||||
"modal.actual.size.setting.info": "※隅棟・谷・棟の実際の寸法を入力してください。",
|
||||
"modal.actual.size.setting.not.exist.auxiliary.line": "실측치 입력할 보조선을 선택해 주세요(JA)",
|
||||
"modal.actual.size.setting.not.exist.size": "실제치수 길이를 입력해 주세요.(JA)",
|
||||
"modal.actual.size.setting.plane.size.length": "廊下の寸法の長さ",
|
||||
"modal.actual.size.setting.actual.size.length": "実寸長",
|
||||
"plan.message.confirm.save": "PLAN을 저장하시겠습니까?",
|
||||
"plan.message.confirm.copy": "PLAN을 복사하시겠습니까?",
|
||||
"plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?",
|
||||
@ -818,7 +824,7 @@
|
||||
"estimate.detail.estimateType": "注文分類",
|
||||
"estimate.detail.roofCns": "屋根材・仕様施工",
|
||||
"estimate.detail.remarks": "備考",
|
||||
"estimate.detail.nextSubmit": "後日資料提出",
|
||||
"estimate.detail.fileFlg": "後日資料提出",
|
||||
"estimate.detail.header.fileList1": "ファイル添付",
|
||||
"estimate.detail.fileList.btn": "ファイル選択",
|
||||
"estimate.detail.header.fileList2": "添付ファイル一覧",
|
||||
@ -835,11 +841,62 @@
|
||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
|
||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
|
||||
"estimate.detail.header.showPrice": "価格表示",
|
||||
"estimate.detail.showPrice.btn1": "Pricing",
|
||||
"estimate.detail.header.unitPrice": "定価",
|
||||
"estimate.detail.showPrice.pricingBtn": "Pricing",
|
||||
"estimate.detail.showPrice.description1": "製品価格 OPEN",
|
||||
"estimate.detail.showPrice.description2": "追加, 変更資材",
|
||||
"estimate.detail.showPrice.description3": "添付必須",
|
||||
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
|
||||
"estimate.detail.showPrice.btn2": "製品を追加",
|
||||
"estimate.detail.showPrice.btn3": "製品削除"
|
||||
"estimate.detail.showPrice.addItem": "製品を追加",
|
||||
"estimate.detail.showPrice.delItem": "製品削除",
|
||||
"estimate.detail.itemTableHeader.dispOrder": "アイテム",
|
||||
"estimate.detail.itemTableHeader.itemId": "品番",
|
||||
"estimate.detail.itemTableHeader.itemNo": "型板",
|
||||
"estimate.detail.itemTableHeader.amount": "数量",
|
||||
"estimate.detail.itemTableHeader.unit": "単位",
|
||||
"estimate.detail.itemTableHeader.salePrice": "単価",
|
||||
"estimate.detail.itemTableHeader.saleTotPrice": "金額 (税別別)",
|
||||
"estimate.detail.docPopup.title": "ドキュメントダウンロードオプションの設定",
|
||||
"estimate.detail.docPopup.explane": "ダウンロードする文書のオプションを選択したら、 [文書のダウンロード]ボタンをクリックします.",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg": "ダウンロードファイル",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0": "見積もり Excel",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1": "定価用 Excel",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2": "見積もり PDF",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3": "定価用 PDF",
|
||||
"estimate.detail.docPopup.schDisplayFlg": "見積提出先表示名",
|
||||
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0": "販売店名",
|
||||
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1": "案件名",
|
||||
"estimate.detail.docPopup.schWeightFlg": "架台重量表を含む",
|
||||
"estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "含む",
|
||||
"estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "含まない",
|
||||
"estimate.detail.docPopup.schDrawingFlg": "図面/シミュレーションファイルを含む",
|
||||
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "含む",
|
||||
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "含まない",
|
||||
"estimate.detail.docPopup.close": "閉じる",
|
||||
"estimate.detail.docPopup.docDownload": "文書のダウンロード",
|
||||
"estimate.detail.productFeaturesPopup.title": "製品特異事項",
|
||||
"estimate.detail.productFeaturesPopup.close": "閉じる",
|
||||
"estimate.detail.save.alertMsg": "保存されている見積書で製品を変更した場合、図面や回路には反映されません.",
|
||||
"estimate.detail.save.requiredMsg": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください.",
|
||||
"estimate.detail.reset.confirmMsg": "保存した見積書情報が初期化され、図面情報が反映されます。本当に初期化しますか?",
|
||||
"simulator.title.sub1": "物件番号",
|
||||
"simulator.title.sub2": "作成日",
|
||||
"simulator.title.sub3": "システム容量",
|
||||
"simulator.title.sub4": "年間予測発電量",
|
||||
"simulator.title.sub5": "都道府県",
|
||||
"simulator.title.sub6": "日射量観測地点",
|
||||
"simulator.title.sub7": "積雪条件",
|
||||
"simulator.title.sub8": "風速条件",
|
||||
"simulator.title.sub9": "以下",
|
||||
"simulator.table.sub1": "屋根面",
|
||||
"simulator.table.sub2": "傾斜角",
|
||||
"simulator.table.sub3": "方位角(度)",
|
||||
"simulator.table.sub4": "太陽電池モジュール",
|
||||
"simulator.table.sub5": "枚数",
|
||||
"simulator.table.sub6": "合計",
|
||||
"simulator.table.sub7": "パワーコンディショナー",
|
||||
"simulator.table.sub8": "台",
|
||||
"simulator.table.sub9": "予測発電量 (kWh)",
|
||||
"simulator.notice.sub1": "Hanwha Japan 年間発電量",
|
||||
"simulator.notice.sub2": "シミュレーション案内事項"
|
||||
}
|
||||
|
||||
@ -290,6 +290,12 @@
|
||||
"modal.flow.direction.setting": "흐름 방향 설정",
|
||||
"modal.flow.direction.setting.info": "흐름방향을 선택하세요.",
|
||||
"modal.image.size.setting": "이미지 크기 조절",
|
||||
"modal.actual.size.setting": "실측치 설정",
|
||||
"modal.actual.size.setting.info": "※隅棟・谷・棟의 실제 치수를 입력해주세요.",
|
||||
"modal.actual.size.setting.not.exist.auxiliary.line": "실측치 입력할 보조선을 선택해 주세요",
|
||||
"modal.actual.size.setting.not.exist.size": "실제치수 길이를 입력해 주세요",
|
||||
"modal.actual.size.setting.plane.size.length": "복도치수 길이",
|
||||
"modal.actual.size.setting.actual.size.length": "실제치수 길이",
|
||||
"plan.message.confirm.save": "PLAN을 저장하시겠습니까?",
|
||||
"plan.message.confirm.copy": "PLAN을 복사하시겠습니까?",
|
||||
"plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?",
|
||||
@ -394,6 +400,9 @@
|
||||
"modal.module.circuit.number.edit": "모듈 일괄 회로 번호 변경",
|
||||
"modal.module.circuit.number.edit.info": "회로 번호를 입력해주세요.",
|
||||
"modal.module.circuit.number": "회로 번호",
|
||||
"modal.line.property.change": "변경할 속성을 선택해 주세요.",
|
||||
"modal.line.property.change.unselect": "변경할 라인을 선택해 주세요.",
|
||||
"modal.line.property.change.confirm": "속성을 변경하시겠습니까?",
|
||||
"common.message.no.data": "No data",
|
||||
"common.message.no.dataDown": "No data to download",
|
||||
"common.message.noData": "No data to display",
|
||||
@ -489,6 +498,7 @@
|
||||
"commons.east": "동",
|
||||
"commons.south": "남",
|
||||
"commons.north": "북",
|
||||
"commons.none": "선택안함",
|
||||
"font.style.normal": "보통",
|
||||
"font.style.italic": "기울임꼴",
|
||||
"font.style.bold": "굵게",
|
||||
@ -824,7 +834,7 @@
|
||||
"estimate.detail.estimateType": "주문분류",
|
||||
"estimate.detail.roofCns": "지붕재・사양시공",
|
||||
"estimate.detail.remarks": "비고",
|
||||
"estimate.detail.nextSubmit": "후일자료제출",
|
||||
"estimate.detail.fileFlg": "후일자료제출",
|
||||
"estimate.detail.header.fileList1": "파일첨부",
|
||||
"estimate.detail.fileList.btn": "파일선택",
|
||||
"estimate.detail.header.fileList2": "첨부파일 목록",
|
||||
@ -841,11 +851,62 @@
|
||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100",
|
||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
|
||||
"estimate.detail.header.showPrice": "가격표시",
|
||||
"estimate.detail.showPrice.btn1": "Pricing",
|
||||
"estimate.detail.header.unitPrice": "정가",
|
||||
"estimate.detail.showPrice.pricingBtn": "Pricing",
|
||||
"estimate.detail.showPrice.description1": "제품 가격 OPEN",
|
||||
"estimate.detail.showPrice.description2": "추가, 변경 자재",
|
||||
"estimate.detail.showPrice.description3": "첨부필수",
|
||||
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
|
||||
"estimate.detail.showPrice.btn2": "제품추가",
|
||||
"estimate.detail.showPrice.btn3": "제품삭제"
|
||||
"estimate.detail.showPrice.addItem": "제품추가",
|
||||
"estimate.detail.showPrice.delItem": "제품삭제",
|
||||
"estimate.detail.itemTableHeader.dispOrder": "Item",
|
||||
"estimate.detail.itemTableHeader.itemId": "품번",
|
||||
"estimate.detail.itemTableHeader.itemNo": "형명",
|
||||
"estimate.detail.itemTableHeader.amount": "수량",
|
||||
"estimate.detail.itemTableHeader.unit": "단위",
|
||||
"estimate.detail.itemTableHeader.salePrice": "단가",
|
||||
"estimate.detail.itemTableHeader.saleTotPrice": "금액(부가세별도)",
|
||||
"estimate.detail.docPopup.title": "문서다운로드 옵션설정",
|
||||
"estimate.detail.docPopup.explane": "다운로드할 문서 옵션을 선택한 후 문서 다운로드 버튼을 클릭합니다.",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg": "다운로드 파일",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0": "견적가 Excel",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1": "정가용 Excel",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2": "견적가 PDF",
|
||||
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3": "정가용 PDF",
|
||||
"estimate.detail.docPopup.schDisplayFlg": "견적제출서 표시명",
|
||||
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0": "판매점명",
|
||||
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1": "안건명",
|
||||
"estimate.detail.docPopup.schWeightFlg": "가대 중량표 포함",
|
||||
"estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "포함",
|
||||
"estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "미포함",
|
||||
"estimate.detail.docPopup.schDrawingFlg": "도면/시뮬레이션 파일 포함",
|
||||
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "포함",
|
||||
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "미포함",
|
||||
"estimate.detail.docPopup.close": "닫기",
|
||||
"estimate.detail.docPopup.docDownload": "문서 다운로드",
|
||||
"estimate.detail.productFeaturesPopup.title": "제품특이사항",
|
||||
"estimate.detail.productFeaturesPopup.close": "닫기",
|
||||
"estimate.detail.save.alertMsg": "저장되었습니다. 견적서에서 제품을 변경할 경우, 도면 및 회로에 반영되지 않습니다.",
|
||||
"estimate.detail.save.requiredMsg": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.",
|
||||
"estimate.detail.reset.confirmMsg": "저장된 견적서 정보가 초기화되고, 도면정보가 반영됩니다. 정말로 초기화 하시겠습니까?",
|
||||
"simulator.title.sub1": "물건번호",
|
||||
"simulator.title.sub2": "작성일",
|
||||
"simulator.title.sub3": "시스템 용량",
|
||||
"simulator.title.sub4": "연간예측발전량",
|
||||
"simulator.title.sub5": "도도부현",
|
||||
"simulator.title.sub6": "일사량 관측지점",
|
||||
"simulator.title.sub7": "적설조건",
|
||||
"simulator.title.sub8": "풍속조건",
|
||||
"simulator.title.sub9": "이하",
|
||||
"simulator.table.sub1": "지붕면",
|
||||
"simulator.table.sub2": "경사각",
|
||||
"simulator.table.sub3": "방위각(도)",
|
||||
"simulator.table.sub4": "태양전지모듈",
|
||||
"simulator.table.sub5": "매수",
|
||||
"simulator.table.sub6": "합계",
|
||||
"simulator.table.sub7": "파워 컨디셔너",
|
||||
"simulator.table.sub8": "대",
|
||||
"simulator.table.sub9": "예측발전량 (kWh)",
|
||||
"simulator.notice.sub1": "Hanwha Japan 연간 발전량",
|
||||
"simulator.notice.sub2": "시뮬레이션 안내사항"
|
||||
}
|
||||
|
||||
@ -185,6 +185,7 @@ export const corridorDimensionSelector = selector({
|
||||
const settingModalFirstOptions = get(settingModalFirstOptionsState)
|
||||
return settingModalFirstOptions.dimensionDisplay.find((option) => option.selected)
|
||||
},
|
||||
dangerouslyAllowMutability: true,
|
||||
})
|
||||
|
||||
// 디스플레이 설정 - 화면 표시
|
||||
|
||||
@ -925,3 +925,32 @@ export function checkLineOrientation(line) {
|
||||
return 'diagonal' // 대각선
|
||||
}
|
||||
}
|
||||
|
||||
// 최상위 parentId를 통해 모든 하위 객체를 찾는 함수
|
||||
export const getAllRelatedObjects = (id, canvas) => {
|
||||
const result = []
|
||||
const map = new Map()
|
||||
|
||||
// Create a map of objects by their id
|
||||
canvas.getObjects().forEach((obj) => {
|
||||
map.set(obj.id, obj)
|
||||
})
|
||||
|
||||
// Helper function to recursively find all related objects
|
||||
function findRelatedObjects(id) {
|
||||
const obj = map.get(id)
|
||||
if (obj) {
|
||||
result.push(obj)
|
||||
canvas.getObjects().forEach((o) => {
|
||||
if (o.parentId === id) {
|
||||
findRelatedObjects(o.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Start the search with the given parentId
|
||||
findRelatedObjects(id)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
import { fabric } from 'fabric'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import {
|
||||
calculateIntersection,
|
||||
distanceBetweenPoints,
|
||||
findClosestPoint,
|
||||
getDegreeByChon,
|
||||
getDirectionByPoint,
|
||||
isPointOnLine,
|
||||
} from '@/util/canvas-util'
|
||||
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util'
|
||||
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import * as turf from '@turf/turf'
|
||||
@ -1177,244 +1170,6 @@ export default function offsetPolygon(vertices, offset) {
|
||||
polygon.canvas.renderAll()
|
||||
})
|
||||
}*/
|
||||
export const splitPolygonWithLines = (polygon) => {
|
||||
const canvas = polygon.canvas
|
||||
polygon.set({ visible: false })
|
||||
let innerLines = [...polygon.innerLines]
|
||||
let polygonLines = [...polygon.lines]
|
||||
const roofs = []
|
||||
|
||||
let delIndexs = []
|
||||
let newLines = []
|
||||
|
||||
polygonLines.forEach((line, index) => {
|
||||
line.tempIndex = index
|
||||
innerLines.forEach((innerLine) => {
|
||||
let newLine1, newLine2
|
||||
if (isPointOnLine(line, innerLine.startPoint)) {
|
||||
// 해당 line을 startPoint로 나눈 line2개를 canvas에 추가 하고 기존 line을 제거한다.
|
||||
newLine1 = new QLine([line.x1, line.y1, innerLine.startPoint.x, innerLine.startPoint.y], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
|
||||
newLine2 = new QLine([innerLine.startPoint.x, innerLine.startPoint.y, line.x2, line.y2], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
delIndexs.push(polygonLines.indexOf(line))
|
||||
canvas.remove(polygonLines[polygonLines.indexOf(line)])
|
||||
if (newLine1.length / 10 > 10) {
|
||||
newLines.push(newLine1)
|
||||
}
|
||||
if (newLine2.length / 10 > 10) {
|
||||
newLines.push(newLine2)
|
||||
}
|
||||
}
|
||||
if (isPointOnLine(line, innerLine.endPoint)) {
|
||||
newLine1 = new QLine([line.x1, line.y1, innerLine.endPoint.x, innerLine.endPoint.y], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
|
||||
newLine2 = new QLine([innerLine.endPoint.x, innerLine.endPoint.y, line.x2, line.y2], {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
delIndexs.push(polygonLines.indexOf(line))
|
||||
canvas.remove(polygonLines[polygonLines.indexOf(line)])
|
||||
if (newLine1.length / 10 > 10) {
|
||||
newLines.push(newLine1)
|
||||
}
|
||||
if (newLine2.length / 10 > 10) {
|
||||
newLines.push(newLine2)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
polygonLines = polygonLines.filter((line) => !delIndexs.includes(line.tempIndex))
|
||||
polygonLines = [...polygonLines, ...newLines]
|
||||
|
||||
const allLines = [...polygonLines, ...innerLines]
|
||||
|
||||
/**
|
||||
* 왼쪽 상단을 startPoint로 전부 변경
|
||||
*/
|
||||
allLines.forEach((line) => {
|
||||
let startPoint // 시작점
|
||||
let endPoint // 끝점
|
||||
if (line.x1 < line.x2) {
|
||||
startPoint = { x: line.x1, y: line.y1 }
|
||||
endPoint = { x: line.x2, y: line.y2 }
|
||||
} else if (line.x1 > line.x2) {
|
||||
startPoint = { x: line.x2, y: line.y2 }
|
||||
endPoint = { x: line.x1, y: line.y1 }
|
||||
} else {
|
||||
if (line.y1 < line.y2) {
|
||||
startPoint = { x: line.x1, y: line.y1 }
|
||||
endPoint = { x: line.x2, y: line.y2 }
|
||||
} else {
|
||||
startPoint = { x: line.x2, y: line.y2 }
|
||||
endPoint = { x: line.x1, y: line.y1 }
|
||||
}
|
||||
}
|
||||
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = endPoint
|
||||
})
|
||||
|
||||
polygonLines.forEach((line) => {
|
||||
const startPoint = line.startPoint // 시작점
|
||||
let arrivalPoint = line.endPoint // 도착점
|
||||
|
||||
let currentPoint = startPoint
|
||||
const roofPoints = [startPoint]
|
||||
|
||||
const startLine = line
|
||||
const visitPoints = [startPoint]
|
||||
const visitLines = [startLine]
|
||||
let cnt = 0
|
||||
|
||||
while (!isSamePoint(currentPoint, arrivalPoint)) {
|
||||
line.set({ stroke: 'red' })
|
||||
canvas.renderAll()
|
||||
let nextLines = allLines.filter(
|
||||
(line2) =>
|
||||
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
|
||||
line !== line2 &&
|
||||
innerLines.includes(line2) &&
|
||||
!visitLines.includes(line2),
|
||||
)
|
||||
|
||||
if (nextLines.length === 0) {
|
||||
nextLines = allLines.filter(
|
||||
(line2) =>
|
||||
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
|
||||
line !== line2 &&
|
||||
!visitLines.includes(line2),
|
||||
)
|
||||
}
|
||||
|
||||
if (!nextLines) {
|
||||
break
|
||||
}
|
||||
|
||||
let comparisonPoints = []
|
||||
|
||||
nextLines.forEach((nextLine) => {
|
||||
if (isSamePoint(nextLine.startPoint, currentPoint)) {
|
||||
comparisonPoints.push(nextLine.endPoint)
|
||||
} else {
|
||||
comparisonPoints.push(nextLine.startPoint)
|
||||
}
|
||||
})
|
||||
|
||||
comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point)))
|
||||
comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint))
|
||||
|
||||
const minDistancePoint = comparisonPoints.reduce((prev, current) => {
|
||||
const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2))
|
||||
const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2))
|
||||
|
||||
return prevDistance < currentDistance ? prev : current
|
||||
}, comparisonPoints[0])
|
||||
|
||||
nextLines.forEach((nextLine) => {
|
||||
if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) {
|
||||
visitLines.push(nextLine)
|
||||
}
|
||||
})
|
||||
|
||||
currentPoint = { ...minDistancePoint }
|
||||
roofPoints.push(currentPoint)
|
||||
cnt++
|
||||
if (cnt > 100) {
|
||||
throw new Error('무한루프')
|
||||
}
|
||||
}
|
||||
roofs.push(roofPoints)
|
||||
})
|
||||
|
||||
const newRoofs = removeDuplicatePolygons(roofs)
|
||||
newRoofs.forEach((roofPoint, index) => {
|
||||
let defense, pitch
|
||||
const polygonLines = [...polygon.lines]
|
||||
|
||||
let representLines = []
|
||||
let representLine
|
||||
|
||||
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
|
||||
polygonLines.forEach((line) => {
|
||||
let startFlag = false
|
||||
let endFlag = false
|
||||
const startPoint = line.startPoint
|
||||
const endPoint = line.endPoint
|
||||
roofPoint.forEach((point, index) => {
|
||||
if (isSamePoint(point, startPoint)) {
|
||||
startFlag = true
|
||||
}
|
||||
if (isSamePoint(point, endPoint)) {
|
||||
endFlag = true
|
||||
}
|
||||
})
|
||||
|
||||
if (startFlag && endFlag) {
|
||||
representLines.push(line)
|
||||
}
|
||||
})
|
||||
|
||||
// representLines중 가장 긴 line을 찾는다.
|
||||
representLines.forEach((line) => {
|
||||
if (!representLine) {
|
||||
representLine = line
|
||||
} else {
|
||||
if (representLine.length < line.length) {
|
||||
representLine = line
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const direction = representLine.direction
|
||||
|
||||
switch (direction) {
|
||||
case 'top':
|
||||
defense = 'east'
|
||||
break
|
||||
case 'right':
|
||||
defense = 'south'
|
||||
break
|
||||
case 'bottom':
|
||||
defense = 'west'
|
||||
break
|
||||
case 'left':
|
||||
defense = 'north'
|
||||
break
|
||||
}
|
||||
pitch = polygon.lines[index].attributes?.pitch ?? 0
|
||||
|
||||
const roof = new QPolygon(roofPoint, {
|
||||
fontSize: polygon.fontSize,
|
||||
stroke: 'black',
|
||||
fill: 'transparent',
|
||||
strokeWidth: 3,
|
||||
name: POLYGON_TYPE.ROOF,
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
selectable: true,
|
||||
defense: defense,
|
||||
direction: defense,
|
||||
pitch: pitch,
|
||||
})
|
||||
|
||||
polygon.canvas.add(roof)
|
||||
canvas.remove(polygon)
|
||||
polygon.canvas.renderAll()
|
||||
})
|
||||
}
|
||||
|
||||
function normalizePoint(point) {
|
||||
return {
|
||||
@ -1432,7 +1187,7 @@ function arePolygonsEqual(polygon1, polygon2) {
|
||||
return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index]))
|
||||
}
|
||||
|
||||
function removeDuplicatePolygons(polygons) {
|
||||
export function removeDuplicatePolygons(polygons) {
|
||||
const uniquePolygons = []
|
||||
|
||||
polygons.forEach((polygon) => {
|
||||
@ -1477,6 +1232,42 @@ function calculateAngleBetweenLines(line1, line2) {
|
||||
return (angleInRadians * 180) / Math.PI
|
||||
}
|
||||
|
||||
/**
|
||||
* 한쪽흐름 지붕
|
||||
* @param roofId
|
||||
* @param canvas
|
||||
*/
|
||||
export const drawShedRoof = (roofId, canvas) => {
|
||||
const roof = canvas?.getObjects().find((object) => object.id === roofId)
|
||||
const hasNonParallelLines = roof.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
|
||||
if (hasNonParallelLines.length > 0) {
|
||||
alert('대각선이 존재합니다.')
|
||||
return
|
||||
}
|
||||
|
||||
const sheds = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
|
||||
const eaves = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
||||
const gables = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.GABLE)
|
||||
|
||||
console.log('gable', gables)
|
||||
|
||||
let shedDegree = sheds[0].attributes.degree || 0
|
||||
const shedChon = sheds[0].attributes.pitch || 0
|
||||
|
||||
if (shedDegree === 0) {
|
||||
shedDegree = getDegreeByChon(shedChon)
|
||||
}
|
||||
const getHeight = function (adjust, degree) {
|
||||
return Math.tan(degree * (Math.PI / 180)) * adjust
|
||||
}
|
||||
|
||||
gables.forEach((gable) => {
|
||||
const adjust = gable.attributes.planeSize
|
||||
const height = getHeight(adjust, shedDegree)
|
||||
gable.attributes.actualSize = Math.round(Math.sqrt(Math.pow(adjust, 2) + Math.pow(height, 2)))
|
||||
})
|
||||
}
|
||||
|
||||
export const drawRidgeRoof = (roofId, canvas) => {
|
||||
const roof = canvas?.getObjects().find((object) => object.id === roofId)
|
||||
const hasNonParallelLines = roof.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
|
||||
@ -1509,14 +1300,19 @@ const drawRidge = (roof, canvas) => {
|
||||
prevRoof = index === 0 ? wallLines[wallLines.length - 1] : wallLines[index - 1]
|
||||
nextRoof = index === wallLines.length - 1 ? wallLines[0] : index === wallLines.length ? wallLines[1] : wallLines[index + 1]
|
||||
|
||||
if (prevRoof.direction !== nextRoof.direction && currentWall.length <= currentRoof.length) {
|
||||
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.length })
|
||||
const angle1 = calculateAngle(prevRoof.startPoint, prevRoof.endPoint)
|
||||
const angle2 = calculateAngle(nextRoof.startPoint, nextRoof.endPoint)
|
||||
|
||||
if (Math.abs(angle1 - angle2) === 180 && currentWall.attributes.planeSize <= currentRoof.attributes.planeSize) {
|
||||
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize })
|
||||
}
|
||||
})
|
||||
|
||||
// 지붕의 길이가 짧은 순으로 정렬
|
||||
ridgeRoof.sort((a, b) => a.length - b.length)
|
||||
|
||||
console.log('ridgeRoof', ridgeRoof)
|
||||
|
||||
ridgeRoof.forEach((item) => {
|
||||
if (getMaxRidge(roofLines.length) > roof.ridges.length) {
|
||||
let index = item.index,
|
||||
@ -1538,28 +1334,24 @@ const drawRidge = (roof, canvas) => {
|
||||
let xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)), //x가 같은 내부선
|
||||
yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선
|
||||
|
||||
let ridgeBaseLength = Math.round((currentRoof.length / 2) * 10) / 10, // 지붕의 기반 길이
|
||||
ridgeMaxLength = Math.min(prevRoof.length, nextRoof.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
|
||||
ridgeAcrossLength = Math.round((ridgeMaxLength - currentRoof.length) * 10) / 10 // 맞은편 벽까지의 길이 - 지붕의 기반 길이
|
||||
let ridgeBaseLength = Math.round(currentRoof.attributes.planeSize / 2), // 지붕의 기반 길이
|
||||
ridgeMaxLength = Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
|
||||
ridgeAcrossLength = Math.abs(Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) - currentRoof.attributes.planeSize) // 맞은편 벽까지의 길이 - 지붕의 기반 길이
|
||||
|
||||
console.log('ridgeBaseLength', ridgeBaseLength, 'ridgeMaxLength', ridgeMaxLength, 'ridgeAcrossLength', ridgeAcrossLength)
|
||||
let acrossRoof = anotherRoof
|
||||
.filter((roof) => {
|
||||
if (roof.x1 === roof.x2) {
|
||||
if ((nextRoof.direction === 'right' && roof.x1 > currentRoof.x1) || (nextRoof.direction === 'left' && roof.x1 < currentRoof.x1)) {
|
||||
return roof
|
||||
}
|
||||
}
|
||||
if (roof.y1 === roof.y2) {
|
||||
if ((nextRoof.direction === 'top' && roof.y1 < currentRoof.y1) || (nextRoof.direction === 'bottom' && roof.y1 > currentRoof.y1)) {
|
||||
return roof
|
||||
}
|
||||
const angle1 = calculateAngle(currentRoof.startPoint, currentRoof.endPoint)
|
||||
const angle2 = calculateAngle(roof.startPoint, roof.endPoint)
|
||||
if (Math.abs(angle1 - angle2) === 180) {
|
||||
return roof
|
||||
}
|
||||
})
|
||||
.reduce((prev, current) => {
|
||||
let hasBetweenRoof = false
|
||||
if (current.x1 === current.x2) {
|
||||
hasBetweenRoof = roofLines
|
||||
.filter((roof) => roof !== current && roof !== currentRoof)
|
||||
.filter((roof) => roof !== current)
|
||||
.some((line) => {
|
||||
let currentY2 = currentRoof.y2
|
||||
if (yEqualInnerLines.length > 0) {
|
||||
@ -1571,12 +1363,13 @@ const drawRidge = (roof, canvas) => {
|
||||
const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentRoof.y1)
|
||||
const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < current.x1) || (line.x1 > currentRoof.x1 && line.x1 < current.x1)
|
||||
const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1)
|
||||
|
||||
return isY1Between && isY2Between && isX1Between && isX2Between
|
||||
})
|
||||
}
|
||||
if (current.y1 === current.y2) {
|
||||
hasBetweenRoof = wallLines
|
||||
.filter((roof) => roof !== current && roof !== currentRoof)
|
||||
hasBetweenRoof = roofLines
|
||||
.filter((roof) => roof !== current)
|
||||
.some((line) => {
|
||||
let currentX2 = currentRoof.x2
|
||||
if (xEqualInnerLines.length > 0) {
|
||||
@ -1614,20 +1407,25 @@ const drawRidge = (roof, canvas) => {
|
||||
}
|
||||
}
|
||||
}, undefined)
|
||||
|
||||
if (acrossRoof !== undefined) {
|
||||
if (currentRoof.x1 === currentRoof.x2) {
|
||||
if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) {
|
||||
ridgeAcrossLength = Math.round((Math.round(Math.abs(currentRoof.x1 - acrossRoof.x1) * 10) / 10 - currentRoof.length) * 10) / 10
|
||||
ridgeAcrossLength = Math.round(Math.round(Math.abs(currentRoof.x1 - acrossRoof.x1) * 10) - currentRoof.attributes.planeSize)
|
||||
}
|
||||
}
|
||||
if (currentRoof.y1 === currentRoof.y2) {
|
||||
if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) {
|
||||
ridgeAcrossLength = Math.round((Math.round(Math.abs(currentRoof.y1 - acrossRoof.y1) * 10) / 10 - currentRoof.length) * 10) / 10
|
||||
ridgeAcrossLength = Math.round(Math.round(Math.abs(currentRoof.y1 - acrossRoof.y1) * 10) - currentRoof.attributes.planeSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ridgeBaseLength = ridgeBaseLength / 10
|
||||
ridgeMaxLength = ridgeMaxLength / 10
|
||||
ridgeAcrossLength = ridgeAcrossLength / 10
|
||||
|
||||
console.log('ridgeBaseLength', ridgeBaseLength, 'ridgeMaxLength', ridgeMaxLength, 'ridgeAcrossLength', ridgeAcrossLength)
|
||||
|
||||
if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) {
|
||||
let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
|
||||
if (currentRoof.x1 === currentRoof.x2) {
|
||||
@ -1769,6 +1567,9 @@ const drawRidge = (roof, canvas) => {
|
||||
attributes: { roofId: roof.id },
|
||||
},
|
||||
)
|
||||
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
|
||||
canvas.add(ridge)
|
||||
roof.ridges.push(ridge)
|
||||
roof.innerLines.push(ridge)
|
||||
@ -1809,6 +1610,8 @@ const drawRidge = (roof, canvas) => {
|
||||
roof.ridges = roof.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
|
||||
roof.innerLines = roof.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
|
||||
roof.innerLines = roof.innerLines.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
|
||||
newRidge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
newRidge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
canvas.add(newRidge)
|
||||
roof.ridges.push(newRidge)
|
||||
roof.innerLines.push(newRidge)
|
||||
@ -1884,7 +1687,7 @@ const drawHips = (roof, canvas) => {
|
||||
stroke: 'red',
|
||||
strokeWidth: 1,
|
||||
name: LINE_TYPE.SUBLINE.HIP,
|
||||
attributes: { roofId: roof.id, currentRoof: currentRoof.id },
|
||||
attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
|
||||
})
|
||||
canvas.add(hip1)
|
||||
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
||||
@ -1901,12 +1704,7 @@ const drawHips = (roof, canvas) => {
|
||||
stroke: 'red',
|
||||
strokeWidth: 1,
|
||||
name: LINE_TYPE.SUBLINE.HIP,
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoof: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
},
|
||||
attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
|
||||
})
|
||||
canvas.add(hip2)
|
||||
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
||||
@ -1977,16 +1775,11 @@ const drawHips = (roof, canvas) => {
|
||||
stroke: 'red',
|
||||
strokeWidth: 1,
|
||||
name: LINE_TYPE.SUBLINE.HIP,
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoof: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
},
|
||||
attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
|
||||
})
|
||||
canvas.add(hip)
|
||||
const hipBase = ((Math.abs(hip.x1 - hip.x2) + Math.abs(hip.y1 - hip.y2)) / 2) * 10
|
||||
const hipHeight = Math.round(hipBase / Math.tan(((90 - currentRoof.attributes.degree) * Math.PI) / 180))
|
||||
const hipHeight = Math.round(hipBase / Math.tan(((90 - currentDegree) * Math.PI) / 180))
|
||||
hip.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip.x1 - hip.x2, 2) + Math.pow(hip.y1 - hip.y2, 2))) * 10
|
||||
if (prevDegree === currentDegree) {
|
||||
hip.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip.attributes.planeSize, 2) + Math.pow(hipHeight, 2)))
|
||||
@ -2040,92 +1833,6 @@ const checkValley = (polygon, line1, line2) => {
|
||||
return isValley
|
||||
}
|
||||
|
||||
const getPointInPolygon = (polygon, point, isInclude = false) => {
|
||||
let inside = false
|
||||
let minX = Math.min(polygon[0].x, polygon[1].x, polygon[2].x, polygon[3].x),
|
||||
maxX = Math.max(polygon[0].x, polygon[1].x, polygon[2].x, polygon[3].x),
|
||||
minY = Math.min(polygon[0].y, polygon[1].y, polygon[2].y, polygon[3].y),
|
||||
maxY = Math.max(polygon[0].y, polygon[1].y, polygon[2].y, polygon[3].y)
|
||||
if (!isInclude && minX < point.x && point.x < maxX && minY < point.y && point.y < maxY) {
|
||||
inside = true
|
||||
}
|
||||
if (isInclude && minX <= point.x && point.x <= maxX && minY <= point.y && point.y <= maxY) {
|
||||
inside = true
|
||||
}
|
||||
return inside
|
||||
}
|
||||
|
||||
/**
|
||||
* 라인과 마주하는 다른 라인과의 가장 가까운 거리를 구한다.
|
||||
* @param polygon
|
||||
* @param currentLine 현재 라인
|
||||
* @param dVector 현재 라인의 방향
|
||||
* @returns {*[]|null}
|
||||
*/
|
||||
const getAcrossLine = (polygon, currentLine, dVector) => {
|
||||
let acrossLine
|
||||
switch (dVector) {
|
||||
case 45:
|
||||
acrossLine = polygon.lines
|
||||
.filter((line) => line.x1 > currentLine.x1 && line.y1 <= currentLine.y1)
|
||||
.reduce((prev, current) => {
|
||||
if (prev.length > 0) {
|
||||
return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
}, [])
|
||||
break
|
||||
case 135:
|
||||
acrossLine = polygon.lines
|
||||
.filter((line) => line.x1 > currentLine.x1 && line.y1 >= currentLine.y1)
|
||||
.reduce((prev, current) => {
|
||||
if (prev.length > 0) {
|
||||
return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
}, [])
|
||||
break
|
||||
case 225:
|
||||
acrossLine = polygon.lines
|
||||
.filter((line) => line.x1 < currentLine.x1 && line.y1 >= currentLine.y1)
|
||||
.reduce((prev, current) => {
|
||||
if (prev.length > 0) {
|
||||
return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
}, [])
|
||||
break
|
||||
case 315:
|
||||
acrossLine = polygon.lines
|
||||
.filter((line) => line.x1 < currentLine.x1 && line.y1 <= currentLine.y1)
|
||||
.reduce((prev, current) => {
|
||||
if (prev.length > 0) {
|
||||
return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
}, [])
|
||||
break
|
||||
}
|
||||
return acrossLine
|
||||
}
|
||||
|
||||
/*
|
||||
추녀마루(hip) 중복방지를 위해 마루와 함께 그려진 추녀마루를 확인한다
|
||||
*/
|
||||
const isAlreadyHip = (polygon, line) => {
|
||||
let isAlreadyHip = false
|
||||
polygon.hips.forEach((hip) => {
|
||||
if (line.x1 === hip.x1 && line.y1 === hip.y1) {
|
||||
isAlreadyHip = true
|
||||
}
|
||||
})
|
||||
return isAlreadyHip
|
||||
}
|
||||
|
||||
/*
|
||||
3개 이상 이어지지 않은 라인 포인트 계산
|
||||
모임지붕에서 point는 3개 이상의 라인과 접해야 함.
|
||||
@ -2216,6 +1923,8 @@ const connectLinePoint = (polygon) => {
|
||||
stroke: 'purple',
|
||||
strokeWidth: 1,
|
||||
})
|
||||
line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) / 10
|
||||
line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) / 10
|
||||
polygon.canvas.add(line)
|
||||
polygon.innerLines.push(line)
|
||||
})
|
||||
@ -2272,6 +1981,8 @@ const connectLinePoint = (polygon) => {
|
||||
stroke: 'purple',
|
||||
strokeWidth: 1,
|
||||
})
|
||||
line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10)
|
||||
line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10)
|
||||
polygon.canvas.add(line)
|
||||
polygon.innerLines.push(line)
|
||||
})
|
||||
@ -2576,6 +2287,8 @@ const changeEavesRoof = (currentRoof, canvas) => {
|
||||
hipX2 = midX - addHipX2
|
||||
hipY2 = midY - addHipY2
|
||||
}
|
||||
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
}
|
||||
|
||||
hipLines.forEach((hip) => {
|
||||
@ -2753,6 +2466,8 @@ const changeGableRoof = (currentRoof, canvas) => {
|
||||
})
|
||||
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
||||
}
|
||||
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
|
||||
let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX, midY], {
|
||||
fontSize: roof.fontSize,
|
||||
@ -2814,6 +2529,14 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
|
||||
if (wallLine.length > 0) {
|
||||
wallLine = wallLine[0]
|
||||
}
|
||||
let prevRoof, nextRoof
|
||||
roof.lines.forEach((r, index) => {
|
||||
if (r.id === currentRoof.id) {
|
||||
currentRoof = r
|
||||
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
||||
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
||||
}
|
||||
})
|
||||
|
||||
const midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
|
||||
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
|
||||
@ -2927,6 +2650,8 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
|
||||
})
|
||||
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
||||
}
|
||||
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
}
|
||||
|
||||
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX + hipX2, midY + hipY2], {
|
||||
@ -2937,10 +2662,15 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoof: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
},
|
||||
})
|
||||
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
||||
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
||||
|
||||
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
||||
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
||||
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
||||
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
||||
canvas?.add(hip1)
|
||||
roof.innerLines.push(hip1)
|
||||
|
||||
@ -2956,6 +2686,10 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
|
||||
actualSize: currentRoof.length,
|
||||
},
|
||||
})
|
||||
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
||||
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
||||
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
||||
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
||||
canvas?.add(hip2)
|
||||
roof.innerLines.push(hip2)
|
||||
|
||||
@ -2974,7 +2708,7 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
|
||||
})
|
||||
})
|
||||
|
||||
hipLines.forEach((hip) => {
|
||||
hipLines.forEach((hip, i) => {
|
||||
const gableLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], {
|
||||
fontSize: roof.fontSize,
|
||||
stroke: 'red',
|
||||
@ -2983,10 +2717,15 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoof: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
const gableDegree = i === 0 ? prevDegree : nextDegree
|
||||
const gableBase = ((Math.abs(gableLine.x1 - gableLine.x2) + Math.abs(gableLine.y1 - gableLine.y2)) / 2) * 10
|
||||
const gableHeight = Math.round(gableBase / Math.tan(((90 - gableDegree) * Math.PI) / 180))
|
||||
gableLine.attributes.planeSize =
|
||||
Math.round(Math.sqrt(Math.pow(gableLine.x1 - gableLine.x2, 2) + Math.pow(gableLine.y1 - gableLine.y2, 2))) * 10
|
||||
gableLine.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gableLine.attributes.planeSize, 2) + Math.pow(gableHeight, 2)))
|
||||
canvas?.add(gableLine)
|
||||
roof.innerLines.push(gableLine)
|
||||
})
|
||||
@ -3016,6 +2755,18 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
wallLine = wallLine[0]
|
||||
}
|
||||
|
||||
let prevRoof, nextRoof
|
||||
roof.lines.forEach((r, index) => {
|
||||
if (r.id === currentRoof.id) {
|
||||
currentRoof = r
|
||||
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
||||
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
||||
}
|
||||
})
|
||||
|
||||
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
||||
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
||||
|
||||
const midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
|
||||
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
|
||||
const midWallX = (wallLine.x1 + wallLine.x2) / 2 // 벽의 X 중심
|
||||
@ -3137,6 +2888,8 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
hipX2 = midX - xWidth
|
||||
hipY2 = midY - yWidth
|
||||
}
|
||||
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
}
|
||||
|
||||
let hipX1 = (Math.sign(currentRoof.x1 - midX) * currentRoof.attributes.width) / 2
|
||||
@ -3150,10 +2903,14 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoofId: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
const gableDegree = currentRoof.attributes.degree > 0 ? currentRoof.attributes.degree : getDegreeByChon(currentRoof.attributes.pitch)
|
||||
const gable1Base = ((Math.abs(gable1.x1 - gable1.x2) + Math.abs(gable1.y1 - gable1.y2)) / 2) * 10
|
||||
const gable1Height = Math.round(gable1Base / Math.tan(((90 - gableDegree) * Math.PI) / 180))
|
||||
gable1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable1.x1 - gable1.x2, 2) + Math.pow(gable1.y1 - gable1.y2, 2))) * 10
|
||||
gable1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable1.attributes.planeSize, 2) + Math.pow(gable1Height, 2)))
|
||||
canvas?.add(gable1)
|
||||
roof.innerLines.push(gable1)
|
||||
|
||||
@ -3168,10 +2925,13 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoofId: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
const gable2Base = ((Math.abs(gable2.x1 - gable2.x2) + Math.abs(gable2.y1 - gable2.y2)) / 2) * 10
|
||||
const gable2Height = Math.round(gable2Base / Math.tan(((90 - gableDegree) * Math.PI) / 180))
|
||||
gable2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable2.x1 - gable2.x2, 2) + Math.pow(gable2.y1 - gable2.y2, 2))) * 10
|
||||
gable2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable2.attributes.planeSize, 2) + Math.pow(gable2Height, 2)))
|
||||
canvas?.add(gable2)
|
||||
roof.innerLines.push(gable2)
|
||||
|
||||
@ -3183,10 +2943,11 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoofId: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
gable3.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10
|
||||
gable3.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10
|
||||
canvas?.add(gable3)
|
||||
roof.innerLines.push(gable3)
|
||||
|
||||
@ -3198,10 +2959,13 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoofId: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
||||
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
||||
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
||||
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
||||
canvas?.add(hip1)
|
||||
roof.innerLines.push(hip1)
|
||||
|
||||
@ -3213,10 +2977,13 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoofId: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
||||
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
||||
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
||||
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
||||
canvas?.add(hip2)
|
||||
roof.innerLines.push(hip2)
|
||||
}
|
||||
@ -3312,6 +3079,9 @@ const changeWallRoof = (currentRoof, canvas) => {
|
||||
canvas?.remove(line)
|
||||
})
|
||||
|
||||
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
||||
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
||||
|
||||
if (currentRoof.attributes.sleeve && currentRoof.attributes.width > 0 && prevRoof.attributes.offset > 0 && nextRoof.attributes.offset > 0) {
|
||||
const prevSignX = Math.sign(prevRoof.x1 - prevRoof.x2)
|
||||
const prevSignY = Math.sign(prevRoof.y1 - prevRoof.y2)
|
||||
@ -3417,6 +3187,8 @@ const changeWallRoof = (currentRoof, canvas) => {
|
||||
y2: ridge.y2 - diffY,
|
||||
})
|
||||
}
|
||||
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
||||
|
||||
let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX, wallMidY], {
|
||||
fontSize: roof.fontSize,
|
||||
@ -3426,10 +3198,14 @@ const changeWallRoof = (currentRoof, canvas) => {
|
||||
attributes: {
|
||||
roofId: roof.id,
|
||||
currentRoofId: currentRoof.id,
|
||||
planeSize: currentRoof.length,
|
||||
actualSize: currentRoof.length,
|
||||
actualSize: 0,
|
||||
},
|
||||
})
|
||||
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
||||
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
||||
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
||||
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
||||
|
||||
let hip2 = new QLine([currentRoof.x2, currentRoof.y2, wallMidX, wallMidY], {
|
||||
fontSize: roof.fontSize,
|
||||
stroke: 'red',
|
||||
@ -3442,6 +3218,10 @@ const changeWallRoof = (currentRoof, canvas) => {
|
||||
actualSize: currentRoof.length,
|
||||
},
|
||||
})
|
||||
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
||||
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
||||
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
||||
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
||||
canvas?.add(hip1)
|
||||
canvas?.add(hip2)
|
||||
roof.innerLines.push(hip1)
|
||||
@ -3505,8 +3285,13 @@ export const changeCurrentRoof = (currentRoof, canvas) => {
|
||||
newRoof.setWall(wall)
|
||||
|
||||
newRoof.lines.forEach((line, index) => {
|
||||
const lineLength = Math.sqrt(
|
||||
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
|
||||
)
|
||||
line.attributes = {
|
||||
roofId: newRoof.id,
|
||||
planeSize: lineLength,
|
||||
actualSize: lineLength,
|
||||
wallLine: wall.lines[index].id,
|
||||
type: wall.lines[index].attributes.type,
|
||||
offset: wall.lines[index].attributes.offset,
|
||||
@ -3561,6 +3346,19 @@ const reDrawPolygon = (polygon, canvas) => {
|
||||
line.attributes = l.attributes
|
||||
}
|
||||
})
|
||||
const lineLength = Math.sqrt(
|
||||
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
|
||||
)
|
||||
if (line.attributes !== undefined) {
|
||||
line.attributes.planeSize = lineLength
|
||||
line.attributes.actualSize = line
|
||||
} else {
|
||||
line.attributes = {
|
||||
roofId: newPolygon.id,
|
||||
planeSize: lineLength,
|
||||
actualSize: lineLength,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
canvas?.add(newPolygon)
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { checkSession } from '@/lib/user'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export const initCheck = async () => {
|
||||
const { session } = await checkSession()
|
||||
|
||||
if (!session.isLoggedIn) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
17
yarn.lock
17
yarn.lock
@ -527,6 +527,11 @@
|
||||
resolved "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz"
|
||||
integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA==
|
||||
|
||||
"@kurkle/color@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
|
||||
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
|
||||
|
||||
"@mapbox/node-pre-gyp@^1.0.0":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
|
||||
@ -4337,6 +4342,13 @@ chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chart.js@^4.4.6:
|
||||
version "4.4.6"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.6.tgz#da39b84ca752298270d4c0519675c7659936abec"
|
||||
integrity sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==
|
||||
dependencies:
|
||||
"@kurkle/color" "^0.3.0"
|
||||
|
||||
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
|
||||
@ -5832,6 +5844,11 @@ rbush@^3.0.1:
|
||||
dependencies:
|
||||
quickselect "^2.0.0"
|
||||
|
||||
react-chartjs-2@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d"
|
||||
integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==
|
||||
|
||||
react-color-palette@^7.2.2:
|
||||
version "7.2.2"
|
||||
resolved "https://registry.npmjs.org/react-color-palette/-/react-color-palette-7.2.2.tgz"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user