Merge branch 'dev'

This commit is contained in:
yoosangwook 2024-11-11 11:09:43 +09:00
commit f5c7c7cb1b
44 changed files with 2844 additions and 895 deletions

View File

@ -12,6 +12,7 @@
"@nextui-org/react": "^2.4.2", "@nextui-org/react": "^2.4.2",
"ag-grid-react": "^32.0.2", "ag-grid-react": "^32.0.2",
"axios": "^1.7.3", "axios": "^1.7.3",
"chart.js": "^4.4.6",
"fabric": "^5.3.0", "fabric": "^5.3.0",
"framer-motion": "^11.2.13", "framer-motion": "^11.2.13",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
@ -22,6 +23,7 @@
"next": "14.2.3", "next": "14.2.3",
"next-international": "^1.2.4", "next-international": "^1.2.4",
"react": "^18", "react": "^18",
"react-chartjs-2": "^5.2.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-datepicker": "^7.3.0", "react-datepicker": "^7.3.0",
"react-dom": "^18", "react-dom": "^18",

View File

@ -13,7 +13,6 @@ export async function GET(req) {
const decodeUrl = decodeURIComponent(targetUrl) const decodeUrl = decodeURIComponent(targetUrl)
const response = await fetch(decodeUrl) const response = await fetch(decodeUrl)
const data = await response.arrayBuffer() const data = await response.arrayBuffer()
const buffer = Buffer.from(data) const buffer = Buffer.from(data)

View File

@ -0,0 +1,9 @@
import Simulator from '@/components/simulator/Simulator'
export default function SimulatorPage() {
return (
<>
<Simulator />
</>
)
}

View File

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

View File

@ -159,6 +159,9 @@ export const SAVE_KEY = [
'groupName', 'groupName',
'lineDirection', 'lineDirection',
'groupId', 'groupId',
'planeSize',
'actualSize',
'surfaceId',
] ]
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype]

View File

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

View File

@ -431,7 +431,25 @@ export default function Roof2(props) {
{ x: 450, y: 850 }, { 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', fill: 'transparent',
stroke: 'green', stroke: 'green',
strokeWidth: 1, strokeWidth: 1,

View File

@ -3,61 +3,73 @@
import { useEffect, useState, useContext } from 'react' import { useEffect, useState, useContext } from 'react'
import { useRecoilValue } from 'recoil' import { useRecoilValue } from 'recoil'
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom' import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { SessionContext } from '@/app/SessionProvider'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import SingleDatePicker from '../common/datepicker/SingleDatePicker' import SingleDatePicker from '../common/datepicker/SingleDatePicker'
import EstimateFileUploader from './EstimateFileUploader' import EstimateFileUploader from './EstimateFileUploader'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom' 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 dayjs from 'dayjs'
import { useCommonCode } from '@/hooks/common/useCommonCode' import { useCommonCode } from '@/hooks/common/useCommonCode'
import Select from 'react-select'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' 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 }) { export default function Estimate({ params }) {
const { session } = useContext(SessionContext)
const [objectNo, setObjectNo] = useState('') // const [objectNo, setObjectNo] = useState('') //
const [planNo, setPlanNo] = useState('') // const [planNo, setPlanNo] = useState('') //
const [files, setFiles] = useState([]) // const [files, setFiles] = useState([]) //
const [originFiles, setOriginFiles] = useState([]) //
const [showContentCode, setShowContentCode] = useState('ATTR001') const [showContentCode, setShowContentCode] = useState('ATTR001')
const [productFeaturesPopupOpen, setProductFeaturesPopupOpen] = useState(false) //
const [showProductFeatureData, setShowProductFeatureData] = useState([]) //
// //
const [hidden, setHidden] = useState(false) const [hidden, setHidden] = useState(false)
//
const [displayItemList, setDisplayItemList] = useState([])
// //
const { findCommonCode } = useCommonCode() const { findCommonCode } = useCommonCode()
const [honorificCodeList, setHonorificCodeList] = useState([]) // const [honorificCodeList, setHonorificCodeList] = useState([]) //
const [storePriceList, setStorePriceList] = useState([]) // option
const [tempPriceCd, setTempPriceCd] = useState('')
const [startDate, setStartDate] = useState(new Date()) const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = { const singleDatePickerProps = {
startDate, startDate,
setStartDate, setStartDate,
} }
const { session } = useContext(SessionContext)
const objectRecoil = useRecoilValue(floorPlanObjectState) const objectRecoil = useRecoilValue(floorPlanObjectState)
// //
const { state, setState } = useEstimateController(params.pid) const { state, setState } = useEstimateController(params.pid)
// LIST const [itemList, setItemList] = useState([]) //
// List // List
const [specialNoteList, setSpecialNoteList] = useState([]) const [specialNoteList, setSpecialNoteList] = useState([])
const globalLocaleState = useRecoilValue(globalLocaleStore) const globalLocaleState = useRecoilValue(globalLocaleStore)
const { get, post } = useAxios(globalLocaleState) const { get, promisePost } = useAxios(globalLocaleState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { setMenuNumber } = useCanvasMenu() const { setMenuNumber } = useCanvasMenu()
// props
const fileUploadProps = { const fileUploadProps = {
// objectNo: '',
// planNo: params.pid,
// category: '10',
uploadFiles: files, uploadFiles: files,
setUploadFiles: setFiles, setUploadFiles: setFiles,
} }
@ -72,6 +84,17 @@ export default function Estimate({ params }) {
if (code1 != null) { if (code1 != null) {
setHonorificCodeList(code1) 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(() => { useEffect(() => {
@ -123,24 +146,130 @@ export default function Estimate({ params }) {
event.stopPropagation() event.stopPropagation()
} }
// state // state
useEffect(() => { useEffect(() => {
// console.log(files) if (isNotEmptyArray(files)) {
if (files.length > 0) {
files.map((row) => { files.map((row) => {
setState({ fileList: row.data }) setState({ fileList: row.data })
}) })
} else { } else {
console.log('첨부파일 없음')
setState({ fileList: [] }) setState({ fileList: [] })
} }
}, [files]) }, [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 ( return (
<div className="sub-content estimate"> <div className="sub-content estimate">
<div className="sub-content-inner"> <div className="sub-content-inner">
{/* 물건번호, 견적서번호, 등록일, 변경일시 시작 */} {/* 물건번호, 견적서번호, 등록일, 변경일시 시작 */}
{/* <form onSubmit={handleSubmit(onValid)}> */}
<div className="sub-content-box"> <div className="sub-content-box">
<div className="sub-table-box"> <div className="sub-table-box">
<div className="estimate-list-wrap one"> <div className="estimate-list-wrap one">
@ -188,7 +317,7 @@ export default function Estimate({ params }) {
<tr> <tr>
{/* 1차 판매점명 */} {/* 1차 판매점명 */}
<th>{getMessage('estimate.detail.saleStoreId')}</th> <th>{getMessage('estimate.detail.saleStoreId')}</th>
<td></td> <td>{state?.firstSaleStoreName}</td>
{/* 견적일 */} {/* 견적일 */}
<th> <th>
{getMessage('estimate.detail.estimateDate')} <span className="important">*</span> {getMessage('estimate.detail.estimateDate')} <span className="important">*</span>
@ -202,7 +331,7 @@ export default function Estimate({ params }) {
<tr> <tr>
{/* 2차 판매점명 */} {/* 2차 판매점명 */}
<th>{getMessage('estimate.detail.otherSaleStoreId')}</th> <th>{getMessage('estimate.detail.otherSaleStoreId')}</th>
<td></td> <td>{state?.agencySaleStoreName}</td>
{/* 담당자 */} {/* 담당자 */}
<th> <th>
{getMessage('estimate.detail.receiveUser')} <span className="important">*</span> {getMessage('estimate.detail.receiveUser')} <span className="important">*</span>
@ -286,6 +415,7 @@ export default function Estimate({ params }) {
value={'YJSS'} value={'YJSS'}
checked={state?.estimateType === 'YJSS' ? true : false} checked={state?.estimateType === 'YJSS' ? true : false}
onChange={(e) => { onChange={(e) => {
//
setState({ estimateType: e.target.value }) setState({ estimateType: e.target.value })
}} }}
/> />
@ -362,9 +492,18 @@ export default function Estimate({ params }) {
<div className="title-wrap"> <div className="title-wrap">
<h3>{getMessage('estimate.detail.header.fileList1')}</h3> <h3>{getMessage('estimate.detail.header.fileList1')}</h3>
<div className="d-check-box light mr5"> <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' }}> <label htmlFor="next" style={{ color: '#101010' }}>
{getMessage('estimate.detail.nextSubmit')} {getMessage('estimate.detail.fileFlg')}
</label> </label>
</div> </div>
</div> </div>
@ -395,6 +534,23 @@ export default function Estimate({ params }) {
<tbody> <tbody>
<tr> <tr>
<th>{getMessage('estimate.detail.header.fileList2')}</th> <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> </tr>
</tbody> </tbody>
</table> </table>
@ -417,47 +573,48 @@ export default function Estimate({ params }) {
<div className="estimate-check-inner"> <div className="estimate-check-inner">
<div className="special-note-check-wrap"> <div className="special-note-check-wrap">
{/* SpecialNoteList반복문 */} {/* SpecialNoteList반복문 */}
{specialNoteList.map((row) => { {specialNoteList.length > 0 &&
return ( specialNoteList.map((row) => {
<div return (
className="special-note-check-item" <div
onClick={(event) => { className="special-note-check-item"
settingShowContent(row.code, event) onClick={(event) => {
}} settingShowContent(row.code, event)
> }}
<div className="d-check-box light"> >
<input <div className="d-check-box light">
type="checkbox" <input
id={row.code} type="checkbox"
checked={!!row.text} id={row.code}
disabled={row.code === 'ATTR001' ? true : false} checked={!!row.text}
onChange={(event) => { disabled={row.code === 'ATTR001' ? true : false}
setSpecialNoteList((specialNote) => onChange={(event) => {
specialNote.map((temp) => (temp.code === row.code ? { ...temp, text: !temp.text } : temp)), setSpecialNoteList((specialNote) =>
) specialNote.map((temp) => (temp.code === row.code ? { ...temp, text: !temp.text } : temp)),
settingShowContent(row.code, event) )
}} settingShowContent(row.code, event)
/> }}
<label htmlFor={row.code}>{row.codeNm}</label> />
<label htmlFor={row.code}>{row.codeNm}</label>
</div>
</div> </div>
</div> )
) })}
})}
</div> </div>
{/* 견적특이사항 선택한 내용?영역시작 */} {/* 견적특이사항 선택한 내용 영역시작 */}
<div className="calculation-estimate"> <div className="calculation-estimate">
{specialNoteList.map((row) => { {specialNoteList.map((row) => {
if (row.code === showContentCode) { if (row.code === showContentCode) {
return ( return (
<dl> <dl>
<dt>{row.codeNm}</dt> <dt>{row.codeNm}</dt>
<dd>{row.remarks}</dd> <dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd>
</dl> </dl>
) )
} }
})} })}
</div> </div>
{/* 견적특이사항 선택한 내용?영역끝 */} {/* 견적특이사항 선택한 내용 영역끝 */}
</div> </div>
</div> </div>
@ -530,12 +687,29 @@ export default function Estimate({ params }) {
<div className="product-price-wrap"> <div className="product-price-wrap">
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div> <div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
<div className="select-wrap"> <div className="select-wrap">
<select className="select-light" name="" id=""> {session?.storeLvl === '1' ? (
<option value="">111</option> <select
<option value="">222</option> className="select-light"
</select> 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> </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>
<div className="product-edit-wrap"> <div className="product-edit-wrap">
<ul className="product-edit-explane"> <ul className="product-edit-explane">
@ -557,26 +731,140 @@ export default function Estimate({ params }) {
</li> </li>
</ul> </ul>
<div className="product-edit-btn"> <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> <span className="plus"></span>
{getMessage('estimate.detail.showPrice.btn2')} {getMessage('estimate.detail.showPrice.addItem')}
</button> </button>
<button className="btn-origin grey"> <button className="btn-origin grey">
<span className="minus"></span> <span className="minus"></span>
{getMessage('estimate.detail.showPrice.btn3')} {getMessage('estimate.detail.showPrice.delItem')}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
{/* 가격표시영역끝 */} {/* 가격표시영역끝 */}
{/* html테이블시작 */} {/* 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테이블끝 */} {/* html테이블끝 */}
</div> </div>
</div> </div>
{/* 기본정보끝 */} {/* 기본정보끝 */}
{/* </form> */}
</div> </div>
{productFeaturesPopupOpen && (
<ProductFeaturesPop
specialNoteList={specialNoteList}
showProductFeatureData={showProductFeatureData}
setProductFeaturesPopupOpen={setProductFeaturesPopupOpen}
/>
)}
</div> </div>
) )
} }

View File

@ -27,6 +27,7 @@ export default function EstimateFileUploader({ uploadFiles, setUploadFiles }) {
// if (res.data > 0) setUploadFiles([...files, { name: e.target.files[0].name, id: uuidv4() }]) // if (res.data > 0) setUploadFiles([...files, { name: e.target.files[0].name, id: uuidv4() }])
// }) // })
setUploadFiles([...uploadFiles, { data: e.target.files[0], id: uuidv4() }]) setUploadFiles([...uploadFiles, { data: e.target.files[0], id: uuidv4() }])
e.target.value = ''
} }
const deleteFile = (id) => { const deleteFile = (id) => {

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

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

View File

@ -1,8 +1,15 @@
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' import {
import { calculateAngle, drawRidgeRoof, drawHippedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils' 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 * as turf from '@turf/turf'
import { LINE_TYPE } from '@/common/common' import { LINE_TYPE } from '@/common/common'
@ -131,12 +138,14 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
}) })
this.on('removed', () => { 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) => { children.forEach((child) => {
this.canvas.remove(child) this.canvas.remove(child)
}) })
}) })
//QPolygon 좌표 이동시 좌표 재계산
this.on('polygonMoved', () => { this.on('polygonMoved', () => {
//폴리곤일때만 사용 //폴리곤일때만 사용
let matrix = this.calcTransformMatrix() let matrix = this.calcTransformMatrix()
@ -148,8 +157,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
.map((p) => { .map((p) => {
return fabric.util.transformPoint(p, matrix) return fabric.util.transformPoint(p, matrix)
}) })
this.set('points', transformedPoints) this.points = transformedPoints
this.set('pathOffset', { x: this.left, y: this.top }) const { left, top } = this.calcOriginCoords()
this.set('pathOffset', { x: left, y: top })
this.setCoords() this.setCoords()
}) })
@ -169,6 +179,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
offset: 0, offset: 0,
}, },
parent: this, parent: this,
parentId: this.id,
direction: getDirectionByPoint(point, nextPoint), direction: getDirectionByPoint(point, nextPoint),
idx: i + 1, 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 eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] 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 gableOdd = types.filter((type, i) => i % 2 === 0)
const gableEven = types.filter((type, i) => i % 2 === 1) const gableEven = types.filter((type, i) => i % 2 === 1)
const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED) const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED)
@ -199,18 +210,40 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
) { ) {
console.log('박공 지붕') console.log('박공 지붕')
} else if (hasShed) { } else if (hasShed) {
//편류지붕 const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
let shedIndex = 0 const areLinesParallel = function (line1, line2) {
types.forEach((type, i) => { const angle1 = calculateAngle(line1.startPoint, line1.endPoint)
if (type === LINE_TYPE.WALLLINE.SHED) { const angle2 = calculateAngle(line2.startPoint, line2.endPoint)
shedIndex = i 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) if (isShedRoof) {
const shedEven = types.filter((type, i) => i % 2 !== shedIndex % 2) const eaves = this.lines
types.forEach((type, i) => console.log(type, i, i % 2, shedIndex % 2, i % 2 === shedIndex % 2)) .filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
if (shedOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && shedEven.every((type) => type === LINE_TYPE.WALLLINE.GABLE)) { .filter((line) => {
console.log('편류지붕') 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 { } else {
drawRidgeRoof(this.id, this.canvas) drawRidgeRoof(this.id, this.canvas)
@ -757,7 +790,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
setViewLengthText(isView) { setViewLengthText(isView) {
this.canvas this.canvas
?.getObjects() ?.getObjects()
.filter((obj) => obj.name === 'lengthText' && obj.parent === this) .filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
.forEach((text) => { .forEach((text) => {
text.set({ visible: isView }) text.set({ visible: isView })
}) })
@ -770,7 +803,33 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.scaleY = scale this.scaleY = scale
this.addLengthText() 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 }
}, },
}) })

View File

@ -16,11 +16,11 @@ import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/Panel
export default function CanvasFrame() { export default function CanvasFrame() {
const canvasRef = useRef(null) const canvasRef = useRef(null)
const { canvas } = useCanvas('canvas') const { canvas, handleBackImageLoadToCanvas } = useCanvas('canvas')
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize() const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
const currentMenu = useRecoilValue(currentMenuState) const currentMenu = useRecoilValue(currentMenuState)
const { contextMenu, handleClick } = useContextMenu() const { contextMenu, handleClick } = useContextMenu()
const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans } = usePlan() const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans, currentCanvasPlan } = usePlan()
useEvent() useEvent()
const loadCanvas = () => { const loadCanvas = () => {

View File

@ -33,6 +33,8 @@ import useMenu from '@/hooks/common/useMenu'
import { MENU } from '@/common/common' import { MENU } from '@/common/common'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { estimateState } from '@/store/floorPlanObjectAtom'
import DocDownOptionPop from '../estimate/popup/DocDownOptionPop'
export default function CanvasMenu(props) { export default function CanvasMenu(props) {
const { menuNumber, setMenuNumber } = props const { menuNumber, setMenuNumber } = props
@ -53,7 +55,10 @@ export default function CanvasMenu(props) {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const { handleZoomClear, handleZoom } = useCanvasEvent() const { handleZoomClear, handleZoom } = useCanvasEvent()
const { handleMenu } = useMenu() const { handleMenu } = useMenu()
const { handleEstimateSubmit } = useEstimateController() const { handleEstimateSubmit } = useEstimateController()
const estimateRecoilState = useRecoilValue(estimateState)
const [estimatePopupOpen, setEstimatePopupOpen] = useState(false)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { currentCanvasPlan, saveCanvas } = usePlan() const { currentCanvasPlan, saveCanvas } = usePlan()
@ -81,6 +86,9 @@ export default function CanvasMenu(props) {
case 4: case 4:
setType('module') setType('module')
break break
case 6:
router.push(`/floor-plan/simulator/${menu.index}`)
break
} }
if (pathname !== '/floor-plan') router.push('/floor-plan') if (pathname !== '/floor-plan') router.push('/floor-plan')
@ -135,6 +143,38 @@ export default function CanvasMenu(props) {
addPopup(id, 1, <SettingModal01 id={id} />, true) 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(() => { useEffect(() => {
if (globalLocale === 'ko') { if (globalLocale === 'ko') {
setAppMessageState(KO) setAppMessageState(KO)
@ -225,7 +265,7 @@ export default function CanvasMenu(props) {
{menuNumber === 5 && ( {menuNumber === 5 && (
<> <>
<div className="ico-btn-from"> <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 className="ico ico01"></span>
<span>{getMessage('plan.menu.estimate.docDown')}</span> <span>{getMessage('plan.menu.estimate.docDown')}</span>
</button> </button>
@ -233,11 +273,24 @@ export default function CanvasMenu(props) {
<span className="ico ico02"></span> <span className="ico ico02"></span>
<span>{getMessage('plan.menu.estimate.save')}</span> <span>{getMessage('plan.menu.estimate.save')}</span>
</button> </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 className="ico ico03"></span>
<span>{getMessage('plan.menu.estimate.reset')}</span> <span>{getMessage('plan.menu.estimate.reset')}</span>
</button> </button>
<button className="btn-frame gray ico-flx"> {/* )} */}
<button
className="btn-frame gray ico-flx"
onClick={() => {
handleEstimateCopy()
}}
>
<span className="ico ico04"></span> <span className="ico ico04"></span>
<span>{getMessage('plan.menu.estimate.copy')}</span> <span>{getMessage('plan.menu.estimate.copy')}</span>
</button> </button>
@ -263,6 +316,8 @@ export default function CanvasMenu(props) {
<div className={`canvas-depth2-wrap ${menuNumber === 2 || menuNumber === 3 || menuNumber === 4 ? 'active' : ''}`}> <div className={`canvas-depth2-wrap ${menuNumber === 2 || menuNumber === 3 || menuNumber === 4 ? 'active' : ''}`}>
{(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && <MenuDepth01 />} {(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && <MenuDepth01 />}
</div> </div>
{/* 견적서(menuNumber=== 5) 상세화면인경우 문서다운로드 팝업 */}
{estimatePopupOpen && <DocDownOptionPop planNo={estimateRecoilState?.planNo} setEstimatePopupOpen={setEstimatePopupOpen} />}
</div> </div>
) )
} }

View File

@ -5,14 +5,18 @@ import { useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom' import { contextPopupPositionState } from '@/store/popupAtom'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
export default function FlowDirectionSetting(props) { export default function FlowDirectionSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState) const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, pos = contextPopupPosition, target } = props const { id, pos = contextPopupPosition, target } = props
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { closePopup } = usePopup() const { closePopup } = usePopup()
const [compasDeg, setCompasDeg] = useState(360) const [compasDeg, setCompasDeg] = useState(360)
const [flowDirection, setFlowDirection] = useState(target.direction)
const { changeSurfaceFlowDirection } = useSurfaceShapeBatch()
const orientations = [ const orientations = [
// { name: `${getMessage('commons.none')}`, value: 0 },
{ name: `${getMessage('commons.south')}`, value: 360 }, { name: `${getMessage('commons.south')}`, value: 360 },
{ name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 }, { name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 },
{ name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: 45 }, { 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="object-direction-wrap">
<div className="plane-direction"> <div className="plane-direction">
<span className="top">{getMessage('commons.north')}</span> <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> <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> <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> <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> </div>
</div> </div>
@ -111,18 +115,14 @@ export default function FlowDirectionSetting(props) {
key={index} key={index}
className={`circle ${compasDeg === 15 * (12 + index) ? 'act' : ''}`} className={`circle ${compasDeg === 15 * (12 + index) ? 'act' : ''}`}
onClick={() => setCompasDeg(15 * (12 + index))} onClick={() => setCompasDeg(15 * (12 + index))}
> ></div>
<i>{13 - index}</i>
</div>
))} ))}
{Array.from({ length: 180 / 15 - 1 }).map((dot, index) => ( {Array.from({ length: 180 / 15 - 1 }).map((dot, index) => (
<div <div
key={index} key={index}
className={`circle ${compasDeg === 15 * (index + 1) ? 'act' : ''}`} className={`circle ${compasDeg === 15 * (index + 1) ? 'act' : ''}`}
onClick={() => setCompasDeg(15 * (index + 1))} onClick={() => setCompasDeg(15 * (index + 1))}
> ></div>
<i>{24 - index}</i>
</div>
))} ))}
<div className="compas"> <div className="compas">
<div className="compas-arr" style={{ transform: `rotate(${compasDeg}deg)` }}></div> <div className="compas-arr" style={{ transform: `rotate(${compasDeg}deg)` }}></div>
@ -133,7 +133,9 @@ export default function FlowDirectionSetting(props) {
</div> </div>
</div> </div>
<div className="grid-btn-wrap"> <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> </div>
</div> </div>

View File

@ -3,13 +3,18 @@ import { useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom' import { contextPopupPositionState } from '@/store/popupAtom'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup' 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) { export default function LinePropertySetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState) const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, pos = contextPopupPosition } = props const { id, pos = contextPopupPosition, target } = props
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { closePopup } = usePopup() const { closePopup } = usePopup()
const { changeSurfaceLinePropertyEvent, changeSurfaceLineProperty } = useSurfaceShapeBatch()
const { initEvent } = useEvent()
const properties = [ const properties = [
{ name: getMessage('eaves.line'), value: 'eaves' }, { name: getMessage('eaves.line'), value: 'eaves' },
{ name: getMessage('ridge'), value: 'ridge' }, { name: getMessage('ridge'), value: 'ridge' },
@ -29,6 +34,14 @@ export default function LinePropertySetting(props) {
{ name: getMessage('no.setting'), value: 'noSetting' }, { name: getMessage('no.setting'), value: 'noSetting' },
] ]
const [selectedProperty, setSelectedProperty] = useState(null) const [selectedProperty, setSelectedProperty] = useState(null)
useEffect(() => {
changeSurfaceLinePropertyEvent(target)
return () => {
initEvent()
}
}, [])
return ( return (
<WithDraggable isShow={true} pos={pos}> <WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap r mount`}> <div className={`modal-pop-wrap r mount`}>
@ -66,7 +79,9 @@ export default function LinePropertySetting(props) {
</div> </div>
</div> </div>
<div className="grid-btn-wrap"> <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> </div>
</div> </div>

View File

@ -18,7 +18,7 @@ export default function SizeSetting(props) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { closePopup } = usePopup() const { closePopup } = usePopup()
const { resizeObjectBatch } = useObjectBatch({}) const { resizeObjectBatch } = useObjectBatch({})
const { reSizePolygon } = useSurfaceShapeBatch() const { resizeSurfaceShapeBatch } = useSurfaceShapeBatch()
const widthRef = useRef(null) const widthRef = useRef(null)
const heightRef = useRef(null) const heightRef = useRef(null)
@ -36,10 +36,11 @@ export default function SizeSetting(props) {
target.name === BATCH_TYPE.OPENING || target.name === BATCH_TYPE.OPENING ||
target.name === BATCH_TYPE.SHADOW || target.name === BATCH_TYPE.SHADOW ||
target.name === BATCH_TYPE.TRIANGLE_DORMER || target.name === BATCH_TYPE.TRIANGLE_DORMER ||
target.name === BATCH_TYPE.PENTAGON_DORMER || target.name === BATCH_TYPE.PENTAGON_DORMER
target.name === POLYGON_TYPE.ROOF
) { ) {
resizeObjectBatch(settingTarget, target, width, height) resizeObjectBatch(settingTarget, target, width, height)
} else if (target.name === POLYGON_TYPE.ROOF) {
resizeSurfaceShapeBatch(settingTarget, target, width, height)
} }
} }

View File

@ -1,14 +1,20 @@
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide' import { useEffect, useState } from 'react'
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { useRecoilState } from 'recoil' import { useRecoilState } from 'recoil'
import { Fragment, useEffect, useState } from 'react'
import { canvasSettingState } from '@/store/canvasAtom' import { canvasSettingState } from '@/store/canvasAtom'
import { basicSettingState } from '@/store/settingAtom'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup' 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 }) { export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) {
const [objectNo, setObjectNo] = useState('test123241008001') // 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 [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
const { closePopup } = usePopup() const { closePopup } = usePopup()
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) 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 { getMessage } = useMessage()
const { get, post } = useAxios() const { get, post } = useAxios()
@ -487,19 +506,85 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
<tr> <tr>
<th>{getMessage('common.input.file')}</th> <th>{getMessage('common.input.file')}</th>
<td> <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"> <div className="img-edit-wrap">
<label className="img-edit-btn" htmlFor="img_file"> <label className="img-edit-btn" htmlFor="img_file">
<span className="img-edit"></span> <span className="img-edit"></span>
{getMessage('common.input.file.load')} {getMessage('common.input.file.load')}
</label> </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>
<div className="img-name-wrap"> <div className="img-name-wrap">
<input type="text" className="input-origin al-l" defaultValue={''} value={image ? image.name : ''} readOnly /> <input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
{image && <button className="img-check" onClick={() => setImage(null)}></button>} {refImage && <button className="img-check" onClick={() => setRefImage(null)}></button>}
</div> </div>
</div> </div> */}
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

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

View File

@ -9,13 +9,11 @@ import { useRecoilValue, useRecoilState, useSetRecoilState, useResetRecoilState
import { stuffSearchState } from '@/store/stuffAtom' import { stuffSearchState } from '@/store/stuffAtom'
import { queryStringFormatter, isEmptyArray } from '@/util/common-utils' import { queryStringFormatter, isEmptyArray } from '@/util/common-utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { isObjectNotEmpty } from '@/util/common-utils'
import { convertNumberToPriceDecimal } from '@/util/common-utils' import { convertNumberToPriceDecimal } from '@/util/common-utils'
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom' import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
import KO from '@/locales/ko.json' import KO from '@/locales/ko.json'
import JA from '@/locales/ja.json' import JA from '@/locales/ja.json'
import QPagination from '../common/pagination/QPagination' import QPagination from '../common/pagination/QPagination'
import { sessionStore } from '@/store/commonAtom'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
export default function Stuff() { export default function Stuff() {
@ -192,7 +190,11 @@ export default function Stuff() {
}) })
} }
//if (session.storeId === 'T01') {
fetchData() fetchData()
//} else if (stuffSearch.schSelSaleStoreId !== '') {
//fetchData()
//}
} else if (stuffSearchParams?.code === 'M') { } else if (stuffSearchParams?.code === 'M') {
const params = { const params = {
saleStoreId: session?.storeId, saleStoreId: session?.storeId,

View File

@ -59,7 +59,7 @@ export default function StuffDetail() {
standardWindSpeedId: '', // standardWindSpeedId: '', //
verticalSnowCover: '', // verticalSnowCover: '', //
coldRegionFlg: false, //(true : 1 / false : 0) coldRegionFlg: false, //(true : 1 / false : 0)
surfaceType: 'III・IV', //(IIIIV / ) surfaceType: 'Ⅲ・Ⅳ', //( / )
saltAreaFlg: false, // (true : 1 / false : 0) saltAreaFlg: false, // (true : 1 / false : 0)
installHeight: '', // installHeight: '', //
conType: '0', //( / ) conType: '0', //( / )
@ -550,7 +550,7 @@ export default function StuffDetail() {
// surfaceType null // surfaceType null
// form.setValue('surfaceType', '') // form.setValue('surfaceType', '')
// form.setValue('surfaceType', 'IIIIV') // form.setValue('surfaceType', '')
form.setValue('surfaceType', detailData.surfaceType) form.setValue('surfaceType', detailData.surfaceType)
// saltAreaFlg 1 true // saltAreaFlg 1 true
form.setValue('saltAreaFlg', detailData.saltAreaFlg === '1' ? true : false) form.setValue('saltAreaFlg', detailData.saltAreaFlg === '1' ? true : false)
@ -872,6 +872,9 @@ export default function StuffDetail() {
form.setValue('standardWindSpeedId', `WL_${info.windSpeed}`) form.setValue('standardWindSpeedId', `WL_${info.windSpeed}`)
form.setValue('verticalSnowCover', info.verticalSnowCover) form.setValue('verticalSnowCover', info.verticalSnowCover)
form.setValue('surfaceType', info.surfaceType) form.setValue('surfaceType', info.surfaceType)
if (info.surfaceType === 'Ⅱ') {
form.setValue('saltAreaFlg', true)
}
form.setValue('installHeight', info.installHeight) form.setValue('installHeight', info.installHeight)
form.setValue('remarks', info.remarks) form.setValue('remarks', info.remarks)
@ -893,6 +896,15 @@ export default function StuffDetail() {
form.setValue('standardWindSpeedId', info.windSpeed) form.setValue('standardWindSpeedId', info.windSpeed)
} }
//surfaceType & saltAreaFlg
const handleRadioChange = (e) => {
if (e.target.value === 'Ⅱ') {
form.setValue('saltAreaFlg', true)
} else {
form.setValue('saltAreaFlg', false)
}
}
//receiveUser: '', // //receiveUser: '', //
const _receiveUser = watch('receiveUser') const _receiveUser = watch('receiveUser')
//objectName: '', // //objectName: '', //
@ -1561,7 +1573,7 @@ export default function StuffDetail() {
onChange={onSelectionChange2} onChange={onSelectionChange2}
getOptionLabel={(x) => x.saleStoreName} getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId} getOptionValue={(x) => x.saleStoreId}
isDisabled={otherSaleStoreList.length > 1 ? false : true} isDisabled={otherSaleStoreList.length > 0 ? false : true}
isClearable={true} isClearable={true}
value={otherSaleStoreList.filter(function (option) { value={otherSaleStoreList.filter(function (option) {
return option.saleStoreId === otherSelOptions return option.saleStoreId === otherSelOptions
@ -1715,11 +1727,29 @@ export default function StuffDetail() {
<td> <td>
<div className="flx-box"> <div className="flx-box">
<div className="d-check-radio light mr10"> <div className="d-check-radio light mr10">
<input type="radio" name="surfaceType" value="III・IV" id="surfaceType0" {...form.register('surfaceType')} /> <input
<label htmlFor="surfaceType0">IIIIV</label> type="radio"
name="surfaceType"
value="Ⅲ・Ⅳ"
id="surfaceType0"
{...form.register('surfaceType')}
onChange={(e) => {
handleRadioChange(e)
}}
/>
<label htmlFor="surfaceType0"></label>
</div> </div>
<div className="d-check-radio light mr10"> <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> <label htmlFor="surfaceType1"></label>
</div> </div>
<div className="d-check-box light mr5"> <div className="d-check-box light mr5">
@ -2211,11 +2241,29 @@ export default function StuffDetail() {
<td> <td>
<div className="flx-box"> <div className="flx-box">
<div className="d-check-radio light mr10"> <div className="d-check-radio light mr10">
<input type="radio" name="surfaceType" value="III・IV" id="surfaceType0" {...form.register('surfaceType')} /> <input
<label htmlFor="surfaceType0">IIIIV</label> type="radio"
name="surfaceType"
value="Ⅲ・Ⅳ"
id="surfaceType0"
{...form.register('surfaceType')}
onChange={(e) => {
handleRadioChange(e)
}}
/>
<label htmlFor="surfaceType0"></label>
</div> </div>
<div className="d-check-radio light mr10"> <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> <label htmlFor="surfaceType1"></label>
</div> </div>
<div className="d-check-box light mr5"> <div className="d-check-box light mr5">

View File

@ -12,7 +12,6 @@ import { isEmptyArray } from '@/util/common-utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import Link from 'next/link' import Link from 'next/link'
import SingleDatePicker from '../common/datepicker/SingleDatePicker' import SingleDatePicker from '../common/datepicker/SingleDatePicker'
import { sessionStore } from '@/store/commonAtom'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { isObjectNotEmpty } from '@/util/common-utils' import { isObjectNotEmpty } from '@/util/common-utils'
@ -20,7 +19,6 @@ import { SessionContext } from '@/app/SessionProvider'
export default function StuffSearchCondition() { export default function StuffSearchCondition() {
const { session } = useContext(SessionContext) const { session } = useContext(SessionContext)
const sessionState = useRecoilValue(sessionStore)
const setAppMessageState = useSetRecoilState(appMessageStore) const setAppMessageState = useSetRecoilState(appMessageStore)
const globalLocaleState = useRecoilValue(globalLocaleStore) const globalLocaleState = useRecoilValue(globalLocaleStore)
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -163,7 +161,7 @@ export default function StuffSearchCondition() {
} else { } else {
if (session.storeLvl === '1') { if (session.storeLvl === '1') {
//T01 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 { } else {
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}`
} }
@ -186,34 +184,36 @@ export default function StuffSearchCondition() {
setSchSelSaleStoreList(allList) setSchSelSaleStoreList(allList)
setFavoriteStoreList(favList) setFavoriteStoreList(favList)
setShowSaleStoreList(favList) setShowSaleStoreList(favList)
setSchSelSaleStoreId(session?.storeId) // setSchSelSaleStoreId(session?.storeId)
setStuffSearch({ setStuffSearch({
...stuffSearch, ...stuffSearch,
code: 'S', code: 'S',
schSelSaleStoreId: session?.storeId, // schSelSaleStoreId: session?.storeId,
}) })
//T01 2 1 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) => { // get({ url: url }).then((res) => {
if (!isEmptyArray(res)) { // if (!isEmptyArray(res)) {
res.map((row) => { // res.map((row) => {
row.value = row.saleStoreId // row.value = row.saleStoreId
row.label = row.saleStoreName // row.label = row.saleStoreName
}) // })
otherList = res // otherList = res
setOtherSaleStoreList(otherList) // setOtherSaleStoreList(otherList)
} else { // } else {
setOtherSaleStoreList([]) // setOtherSaleStoreList([])
} // }
}) // })
} else { } else {
if (session?.storeLvl === '1') { if (session?.storeLvl === '1') {
allList = res allList = res
favList = res.filter((row) => row.priority !== 'B') favList = res.filter((row) => row.priority !== 'B')
otherList = res.filter((row) => row.firstAgentYn === 'N') otherList = res.filter((row) => row.firstAgentYn === 'N')
setSchSelSaleStoreList(allList) setSchSelSaleStoreList(allList)
setFavoriteStoreList(allList) setFavoriteStoreList(allList)
setShowSaleStoreList(allList) setShowSaleStoreList(allList)
@ -323,6 +323,12 @@ export default function StuffSearchCondition() {
useEffect(() => { useEffect(() => {
setStartDate(stuffSearch?.schFromDt ? stuffSearch.schFromDt : dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD')) 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')) 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]) }, [stuffSearch])
useEffect(() => { useEffect(() => {
@ -385,7 +391,7 @@ export default function StuffSearchCondition() {
ref={objectNoRef} ref={objectNoRef}
className="input-light" className="input-light"
defaultValue={stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo} defaultValue={stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo}
onChange={(e) => { onChange={() => {
setObjectNo(objectNoRef.current.value) setObjectNo(objectNoRef.current.value)
}} }}
onKeyUp={handleByOnKeyUp} onKeyUp={handleByOnKeyUp}
@ -400,7 +406,7 @@ export default function StuffSearchCondition() {
ref={saleStoreNameRef} ref={saleStoreNameRef}
className="input-light" className="input-light"
defaultValue={stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName} defaultValue={stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName}
onChange={(e) => { onChange={() => {
setSaleStoreName(saleStoreNameRef.current.value) setSaleStoreName(saleStoreNameRef.current.value)
}} }}
onKeyUp={handleByOnKeyUp} onKeyUp={handleByOnKeyUp}
@ -415,7 +421,7 @@ export default function StuffSearchCondition() {
ref={addressRef} ref={addressRef}
className="input-light" className="input-light"
defaultValue={stuffSearch?.schAddress ? stuffSearch.schAddress : address} defaultValue={stuffSearch?.schAddress ? stuffSearch.schAddress : address}
onChange={(e) => { onChange={() => {
setAddress(addressRef.current.value) setAddress(addressRef.current.value)
}} }}
onKeyUp={handleByOnKeyUp} onKeyUp={handleByOnKeyUp}
@ -430,7 +436,7 @@ export default function StuffSearchCondition() {
ref={dispCompanyNameRef} ref={dispCompanyNameRef}
className="input-light" className="input-light"
defaultValue={stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName} defaultValue={stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName}
onChange={(e) => { onChange={() => {
setDispCompanyName(dispCompanyNameRef.current.value) setDispCompanyName(dispCompanyNameRef.current.value)
}} }}
onKeyUp={handleByOnKeyUp} onKeyUp={handleByOnKeyUp}
@ -447,7 +453,7 @@ export default function StuffSearchCondition() {
ref={objectNameRef} ref={objectNameRef}
className="input-light" className="input-light"
defaultValue={stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName} defaultValue={stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName}
onChange={(e) => { onChange={() => {
setobjectName(objectNameRef.current.value) setobjectName(objectNameRef.current.value)
}} }}
onKeyUp={handleByOnKeyUp} onKeyUp={handleByOnKeyUp}
@ -462,7 +468,7 @@ export default function StuffSearchCondition() {
className="input-light" className="input-light"
ref={receiveUserRef} ref={receiveUserRef}
defaultValue={stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser} defaultValue={stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser}
onChange={(e) => { onChange={() => {
setReceiveUser(receiveUserRef.current.value) setReceiveUser(receiveUserRef.current.value)
}} }}
onKeyUp={handleByOnKeyUp} onKeyUp={handleByOnKeyUp}
@ -580,7 +586,7 @@ export default function StuffSearchCondition() {
onChange={onSelectionChange2} onChange={onSelectionChange2}
getOptionLabel={(x) => x.saleStoreName} getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId} getOptionValue={(x) => x.saleStoreId}
isDisabled={otherSaleStoreList.length > 1 ? false : true} isDisabled={otherSaleStoreList.length > 0 ? false : true}
isClearable={true} isClearable={true}
value={otherSaleStoreList.filter(function (option) { value={otherSaleStoreList.filter(function (option) {
return option.saleStoreId === otherSaleStoreId return option.saleStoreId === otherSaleStoreId

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

View File

@ -7,6 +7,7 @@ import { useFont } from '@/hooks/common/useFont'
import { useGrid } from '@/hooks/common/useGrid' import { useGrid } from '@/hooks/common/useGrid'
import { globalFontAtom } from '@/store/fontAtom' import { globalFontAtom } from '@/store/fontAtom'
import { useRoof } from '@/hooks/common/useRoof' import { useRoof } from '@/hooks/common/useRoof'
import { usePolygon } from '@/hooks/usePolygon'
export function useCanvasConfigInitialize() { export function useCanvasConfigInitialize() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -20,6 +21,7 @@ export function useCanvasConfigInitialize() {
const {} = useFont() const {} = useFont()
const {} = useGrid() const {} = useGrid()
const {} = useRoof() const {} = useRoof()
const { drawDirectionArrow } = usePolygon()
useEffect(() => { useEffect(() => {
if (!canvas) return if (!canvas) return
@ -173,6 +175,7 @@ export function useCanvasConfigInitialize() {
//그룹 객체로 다시 만든다 (좌표때문에) //그룹 객체로 다시 만든다 (좌표때문에)
const group = new fabric.Group(objectArray, { const group = new fabric.Group(objectArray, {
...objectArray,
groupId: id, groupId: id,
name: objectsName, name: objectsName,
selectable: true, selectable: true,

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

View File

@ -5,6 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom' import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { isObjectNotEmpty } from '@/util/common-utils' import { isObjectNotEmpty } from '@/util/common-utils'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
import { useMessage } from '@/hooks/useMessage'
const reducer = (prevState, nextState) => { const reducer = (prevState, nextState) => {
return { ...prevState, ...nextState } return { ...prevState, ...nextState }
@ -21,35 +22,14 @@ const defaultEstimateData = {
estimateType: 'YJOD', //주문분류 estimateType: 'YJOD', //주문분류
remarks: '', //비고 remarks: '', //비고
estimateOption: '', //견적특이사항 estimateOption: '', //견적특이사항
// itemList: [{ id: 1, name: '' }], itemList: [],
//아이템에 필요없는거 빼기
itemList: [
{
amount: '',
fileUploadFlg: '',
itemChangeFlg: '',
itemGroup: '',
itemId: '', //키값??
itemName: '',
itemNo: '',
moduleFlg: '',
objectNo: '',
pkgMaterialFlg: '',
planNo: '',
pnowW: '',
salePrice: '',
saleTotPrice: '',
specification: '',
unit: '',
},
],
fileList: [], fileList: [],
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
priceCd: '',
} }
// Helper functions // Helper functions
// const updateItemInList = (itemList, id, updates) => {
const updateItemInList = (itemList, itemId, 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)) return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item))
} }
@ -59,6 +39,8 @@ export const useEstimateController = (planNo) => {
const objectRecoil = useRecoilValue(floorPlanObjectState) const objectRecoil = useRecoilValue(floorPlanObjectState)
const [estimateData, setEstimateData] = useRecoilState(estimateState) const [estimateData, setEstimateData] = useRecoilState(estimateState)
const { getMessage } = useMessage()
const { get, post, promisePost } = useAxios(globalLocaleState) const { get, post, promisePost } = useAxios(globalLocaleState)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -87,81 +69,86 @@ export const useEstimateController = (planNo) => {
} }
} }
// const updateItem = (id, updates) => {
const updateItem = (itemId, updates) => { const updateItem = (itemId, updates) => {
setState({ setState({
// itemList: updateItemInList(state.itemList, id, updates),
itemList: updateItemInList(state.itemList, itemId, updates), itemList: updateItemInList(state.itemList, itemId, updates),
}) })
} }
const addItem = () => { const addItem = () => {
// const newId = Math.max(...state.itemList.map((item) => item.id)) + 1 const newItemDispOrder = (Math.max(...state.itemList.map((item) => item.dispOrder)) + 1) * 100
const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1
setState({ setState({
// itemList: [...state.itemList, { id: newId, name: '' }],
//셋팅할필요없는거 빼기
itemList: [ itemList: [
...state.itemList, ...state.itemList,
{ {
itemId: newItemId, dispOrder: newItemDispOrder,
amount: '', itemId: '', //제품번호
fileUploadFlg: '', itemNo: '', //형명
itemChangeFlg: '',
itemGroup: '',
itemName: '', itemName: '',
itemNo: '', amount: '', //수량
moduleFlg: '', unitPrice: '0',
objectNo: '', unit: '', //단위
pkgMaterialFlg: '', salePrice: '0', //단가
planNo: '', saleTotPrice: '0', //금액(부가세별도)
pnowW: '',
salePrice: '',
saleTotPrice: '',
specification: '',
unit: '',
}, },
], ],
}) })
} }
useEffect(() => { useEffect(() => {
setEstimateData({ ...state, userId: session.userId }) setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd: session.custCd })
//sapSalesStoreCd 추가예정 필수값
// setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd : session.sapSalesStoreCd })
}, [state]) }, [state])
//견적서 저장 //견적서 저장
const handleEstimateSubmit = async () => { const handleEstimateSubmit = async () => {
//0. 필수체크
let flag = true
console.log('::담긴 estimateData:::', estimateData) 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) => { //아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼
console.log('파일저장::::::::::', res) if (estimateData.itemList.length > 1) {
}) estimateData.itemList.map((row) => {
if (row.fileUploadFlg === '1') {
//2. 상세데이터 저장 if (estimateData.fileFlg === '0') {
alert(getMessage('estimate.detail.save.requiredMsg'))
console.log('상세저장시작!!') flag = false
return }
try { }
const result = await promisePost({
url: ESTIMATE_API_ENDPOINT,
data: estimateData,
}) })
return result }
} catch (error) { if (flag) {
console.error('Failed to submit estimate:', error) //1. 첨부파일 저장
throw error 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
// }
} }
} }

View File

@ -374,6 +374,9 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer)) const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer))
const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
console.log('trianglePolygon', trianglePolygon)
console.log('selectedSurfacePolygon', selectedSurfacePolygon)
//지붕 밖으로 그렸을때 //지붕 밖으로 그렸을때
if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) { if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) {
swalFire({ text: '도머를 배치할 수 없습니다.', icon: 'error' }) swalFire({ text: '도머를 배치할 수 없습니다.', icon: 'error' })
@ -496,6 +499,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
}) })
canvas?.add(objectGroup) canvas?.add(objectGroup)
objectGroup.getObjects().forEach((obj, index) => {
console.log(`최초 pathOffset ${index}`, obj.get('pathOffset'))
})
isDown = false isDown = false
initEvent() initEvent()
dbClickEvent() dbClickEvent()
@ -917,20 +924,17 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
originY: 'center', originY: 'center',
left: changedCoords.x, left: changedCoords.x,
top: changedCoords.y, top: changedCoords.y,
width: width / 10,
height: height / 10,
}) })
if (target.name === 'roof') { //얘는 일단 도머에 적용함
//얘는 일단 도머에 적용함 if (target.type === 'group') {
if (target.type === 'group') { target._objects.forEach((obj) => setSurfaceShapePattern(obj))
target._objects.forEach((obj) => setSurfaceShapePattern(obj))
} else {
setSurfaceShapePattern(target)
target.fire('modified')
}
} }
// target.setCoords() // target.setCoords()
canvas.renderAll() canvas.renderAll()
if (target.type === 'group') reGroupObject(target) if (target.type === 'group') reGroupObject(target)
} }

View File

@ -5,7 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal' 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 { setSurfaceShapePattern } from '@/util/canvas-util'
import { POLYGON_TYPE } from '@/common/common' import { POLYGON_TYPE } from '@/common/common'
@ -17,6 +17,8 @@ export function useCanvasSetting() {
const { option1, option2, dimensionDisplay } = settingModalFirstOptions const { option1, option2, dimensionDisplay } = settingModalFirstOptions
const { option3, option4 } = settingModalSecondOptions const { option3, option4 } = settingModalSecondOptions
const corridorDimension = useRecoilValue(corridorDimensionSelector)
const globalLocaleState = useRecoilValue(globalLocaleStore) const globalLocaleState = useRecoilValue(globalLocaleStore)
const { get, post } = useAxios(globalLocaleState) const { get, post } = useAxios(globalLocaleState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -27,6 +29,36 @@ export function useCanvasSetting() {
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요 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(() => { useEffect(() => {
console.log('useCanvasSetting useEffect 실행1') console.log('useCanvasSetting useEffect 실행1')
fetchSettings() fetchSettings()
@ -257,7 +289,7 @@ export function useCanvasSetting() {
optionName = ['7'] optionName = ['7']
break break
case 'flowDisplay': //흐름방향 표시 case 'flowDisplay': //흐름방향 표시
optionName = ['arrow'] optionName = ['arrow', 'flowText']
break break
case 'trestleDisplay': //가대 표시 case 'trestleDisplay': //가대 표시
optionName = ['8'] optionName = ['8']

View File

@ -815,7 +815,7 @@ export function useAuxiliaryDrawing(id) {
roofBase.lines.some((line) => isPointOnLine(line, { x: line.x2, y: line.y2 })) roofBase.lines.some((line) => isPointOnLine(line, { x: line.x2, y: line.y2 }))
if (inPolygon1 && inPolygon2) { if (inPolygon1 && inPolygon2) {
line.attributes = { ...line.attributes, roofId: roofBase.id } line.attributes = { ...line.attributes, roofId: roofBase.id, actualSize: 0, planeSize: line.getLength() }
return true return true
} }
}) })

View File

@ -1,23 +1,26 @@
import { useRecoilValue } from 'recoil' import { useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom' import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { setSurfaceShapePattern } from '@/util/canvas-util' import { setSurfaceShapePattern } from '@/util/canvas-util'
import { splitPolygonWithLines } from '@/util/qpolygon-utils'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { roofDisplaySelector } from '@/store/settingAtom' import { roofDisplaySelector } from '@/store/settingAtom'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { POLYGON_TYPE } from '@/common/common' 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) { export function useRoofAllocationSetting(id) {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const roofDisplay = useRecoilValue(roofDisplaySelector) const roofDisplay = useRecoilValue(roofDisplaySelector)
const { drawDirectionArrow } = usePolygon() const { drawDirectionArrow, addLengthText, splitPolygonWithLines } = usePolygon()
const { closePopup } = usePopup() const [popupId, setPopupId] = useState(uuidv4())
const { addPopup, closePopup, closeAll } = usePopup()
const { getMessage } = useMessage()
const currentObject = useRecoilValue(currentObjectState)
const { swalFire } = useSwal() const { swalFire } = useSwal()
const roofMaterials = [ const roofMaterials = [
{ {
id: 'A', id: 'A',
@ -78,21 +81,47 @@ export function useRoofAllocationSetting(id) {
]) ])
const [radioValue, setRadioValue] = useState('A') const [radioValue, setRadioValue] = useState('A')
const [editingLines, setEditingLines] = useState([])
const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0]) const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0])
useEffect(() => { 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) const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
if (roofBases.length === 0) { if (roofBases.length === 0) {
swalFire({ text: '할당할 지붕이 없습니다.' }) swalFire({ text: '할당할 지붕이 없습니다.' })
closePopup(id) closePopup(id)
} }
// if (type === POLYGON_TYPE.ROOF) {
// // 지붕면 할당
//
// } else if ('roof') {
// // 지붕재 변경
// }
}, []) }, [])
const onAddRoofMaterial = () => { const onAddRoofMaterial = () => {
@ -105,6 +134,48 @@ export function useRoofAllocationSetting(id) {
// 선택한 지붕재로 할당 // 선택한 지붕재로 할당
const handleSave = () => { 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 roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
roofBases.forEach((roofBase) => { roofBases.forEach((roofBase) => {
@ -136,8 +207,26 @@ export function useRoofAllocationSetting(id) {
removeTargets.forEach((obj) => { removeTargets.forEach((obj) => {
canvas.remove(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) => { const handleRadioOnChange = (e) => {
@ -149,6 +238,8 @@ export function useRoofAllocationSetting(id) {
onAddRoofMaterial, onAddRoofMaterial,
onDeleteRoofMaterial, onDeleteRoofMaterial,
handleRadioOnChange, handleRadioOnChange,
handleAlloc,
setLineSize,
widths, widths,
lengths, lengths,
rafters, rafters,

View File

@ -167,9 +167,8 @@ export function useRoofShapeSetting(id) {
] ]
const handleSave = () => { const handleSave = () => {
//기존 wallLine 삭제
let outerLines let outerLines
let direction
switch (shapeNum) { switch (shapeNum) {
case 1: { case 1: {
@ -196,6 +195,7 @@ export function useRoofShapeSetting(id) {
// 서쪽 // 서쪽
initLineSetting() initLineSetting()
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
direction = 'west'
outerLines.forEach((line) => { outerLines.forEach((line) => {
setWestAndEastRoof(line) setWestAndEastRoof(line)
@ -240,6 +240,7 @@ export function useRoofShapeSetting(id) {
case 6: { case 6: {
initLineSetting() initLineSetting()
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
direction = 'east'
outerLines.forEach((line) => { outerLines.forEach((line) => {
setWestAndEastRoof(line) setWestAndEastRoof(line)
@ -285,6 +286,7 @@ export function useRoofShapeSetting(id) {
case 7: { case 7: {
initLineSetting() initLineSetting()
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
direction = 'south'
outerLines.forEach((line) => { outerLines.forEach((line) => {
setSouthAndNorthRoof(line) setSouthAndNorthRoof(line)
@ -329,6 +331,7 @@ export function useRoofShapeSetting(id) {
case 8: { case 8: {
initLineSetting() initLineSetting()
outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
direction = 'north'
outerLines.forEach((line) => { outerLines.forEach((line) => {
setSouthAndNorthRoof(line) setSouthAndNorthRoof(line)
@ -374,22 +377,22 @@ export function useRoofShapeSetting(id) {
} }
// 기존 wallLine, roofBase 제거 // 기존 wallLine, roofBase 제거
canvas /*canvas
.getObjects() .getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.WALL) .filter((obj) => obj.name === POLYGON_TYPE.WALL)
.forEach((line) => { .forEach((line) => {
canvas.remove(line) canvas.remove(line)
}) })*/
canvas /*canvas
.getObjects() .getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.ROOF) .filter((obj) => obj.name === POLYGON_TYPE.ROOF)
.forEach((obj) => { .forEach((obj) => {
canvas.remove(...obj.innerLines) canvas.remove(...obj.innerLines)
canvas.remove(obj) canvas.remove(obj)
}) })*/
const polygon = addPolygonByLines(outerLines, { name: POLYGON_TYPE.WALL }) const polygon = addPolygonByLines(outerLines, { name: POLYGON_TYPE.WALL, direction })
polygon.lines = [...outerLines] polygon.lines = [...outerLines]
addPitchTextsByOuterLines() addPitchTextsByOuterLines()
@ -397,7 +400,6 @@ export function useRoofShapeSetting(id) {
canvas?.renderAll() canvas?.renderAll()
roof.drawHelpLine() roof.drawHelpLine()
// setShowRoofShapeSettingModal(false)
isFixRef.current = true isFixRef.current = true
closePopup(id) closePopup(id)
} }

View File

@ -13,6 +13,8 @@ import { usePopup } from '@/hooks/usePopup'
import { roofDisplaySelector } from '@/store/settingAtom' import { roofDisplaySelector } from '@/store/settingAtom'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
import { slopeSelector } from '@/store/commonAtom'
import { QLine } from '@/components/fabric/QLine'
export function useSurfaceShapeBatch() { export function useSurfaceShapeBatch() {
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -22,6 +24,7 @@ export function useSurfaceShapeBatch() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const globalPitch = useRecoilValue(globalPitchState) const globalPitch = useRecoilValue(globalPitchState)
const roofDisplay = useRecoilValue(roofDisplaySelector) const roofDisplay = useRecoilValue(roofDisplaySelector)
const slope = useRecoilValue(slopeSelector(globalPitch))
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { addCanvasMouseEventListener, initEvent } = useEvent() const { addCanvasMouseEventListener, initEvent } = useEvent()
const { closePopup } = usePopup() const { closePopup } = usePopup()
@ -126,6 +129,7 @@ export function useSurfaceShapeBatch() {
addCanvasMouseEventListener('mouse:down', (e) => { addCanvasMouseEventListener('mouse:down', (e) => {
isDrawing = false isDrawing = false
obj.set('name', POLYGON_TYPE.ROOF) obj.set('name', POLYGON_TYPE.ROOF)
obj.set('surfaceId', surfaceId)
initEvent() initEvent()
setSurfaceShapePattern(obj, roofDisplay.column) setSurfaceShapePattern(obj, roofDisplay.column)
closePopup(id) closePopup(id)
@ -580,18 +584,19 @@ export function useSurfaceShapeBatch() {
text: '배치면 내용을 전부 삭제하시겠습니까?', text: '배치면 내용을 전부 삭제하시겠습니까?',
type: 'confirm', type: 'confirm',
confirmFn: () => { confirmFn: () => {
canvas?.getObjects().forEach((obj) => { // canvas?.getObjects().forEach((obj) => {
if ( // if (
obj.name === POLYGON_TYPE.ROOF || // obj.name === POLYGON_TYPE.ROOF ||
obj.name === BATCH_TYPE.OPENING || // obj.name === BATCH_TYPE.OPENING ||
obj.name === BATCH_TYPE.SHADOW || // obj.name === BATCH_TYPE.SHADOW ||
obj.name === BATCH_TYPE.TRIANGLE_DORMER || // obj.name === BATCH_TYPE.TRIANGLE_DORMER ||
obj.name === BATCH_TYPE.PENTAGON_DORMER || // obj.name === BATCH_TYPE.PENTAGON_DORMER ||
obj.name === 'lengthText' // obj.name === 'lengthText'
) { // ) {
canvas?.remove(obj) // canvas?.remove(obj)
} // }
}) // })
canvas.clear()
swalFire({ text: '삭제 완료 되었습니다.' }) swalFire({ text: '삭제 완료 되었습니다.' })
}, },
// denyFn: () => { // denyFn: () => {
@ -657,7 +662,7 @@ export function useSurfaceShapeBatch() {
} }
function getAllRelatedObjects(id) { function getAllRelatedObjects(id) {
const result = [] const ult = []
const map = new Map() const map = new Map()
// Create a map of objects by their id // Create a map of objects by their id
@ -688,52 +693,8 @@ export function useSurfaceShapeBatch() {
const roof = canvas.getActiveObject() const roof = canvas.getActiveObject()
if (roof) { if (roof) {
let isDragging = false
const childrenObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id) 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 selectionArray = [roof, ...childrenObjects]
const selection = new fabric.ActiveSelection(selectionArray, { const selection = new fabric.ActiveSelection(selectionArray, {
@ -748,7 +709,6 @@ export function useSurfaceShapeBatch() {
canvas.setActiveObject(selection) canvas.setActiveObject(selection)
addCanvasMouseEventListener('mouse:up', (e) => { addCanvasMouseEventListener('mouse:up', (e) => {
isDragging = false
canvas.selection = true canvas.selection = true
canvas.discardActiveObject() // 모든 선택 해제 canvas.discardActiveObject() // 모든 선택 해제
@ -768,7 +728,6 @@ export function useSurfaceShapeBatch() {
canvas.renderAll() canvas.renderAll()
roof.fire('polygonMoved') roof.fire('polygonMoved')
if (roof.type === 'group') reGroupObject(obj)
drawDirectionArrow(roof) drawDirectionArrow(roof)
initEvent() initEvent()
}) })
@ -805,9 +764,223 @@ export function useSurfaceShapeBatch() {
canvas?.remove(groupObj) 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 { return {
applySurfaceShape, applySurfaceShape,
deleteAllSurfacesAndObjects, deleteAllSurfacesAndObjects,
moveSurfaceShapeBatch, moveSurfaceShapeBatch,
resizeSurfaceShapeBatch,
surfaceShapeActualSize,
changeSurfaceFlowDirection,
changeSurfaceLinePropertyEvent,
changeSurfaceLineProperty,
} }
} }

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import { v4 as uuidv4 } from 'uuid' 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 { QPolygon } from '@/components/fabric/QPolygon'
import { usePlan } from '@/hooks/usePlan' import { usePlan } from '@/hooks/usePlan'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
@ -12,8 +12,6 @@ export function useCanvasEvent() {
const [canvasForEvent, setCanvasForEvent] = useState(null) const [canvasForEvent, setCanvasForEvent] = useState(null)
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const canvasSize = useRecoilValue(canvasSizeState) const canvasSize = useRecoilValue(canvasSizeState)
const fontSize = useRecoilValue(fontSizeState)
const fontFamily = useRecoilValue(fontFamilyState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const lengthTextOption = useRecoilValue(fontSelector('lengthText')) const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan() const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan()
@ -211,7 +209,7 @@ export function useCanvasEvent() {
setCurrentObject(target) setCurrentObject(target)
const { selected } = e const { selected } = e
if (selected.length > 0) { if (selected?.length > 0) {
selected.forEach((obj) => { selected.forEach((obj) => {
if (obj.type === 'QPolygon') { if (obj.type === 'QPolygon') {
obj.set({ stroke: 'red' }) obj.set({ stroke: 'red' })
@ -224,7 +222,7 @@ export function useCanvasEvent() {
setCurrentObject(null) setCurrentObject(null)
const { deselected } = e const { deselected } = e
if (deselected.length > 0) { if (deselected?.length > 0) {
deselected.forEach((obj) => { deselected.forEach((obj) => {
if (obj.type === 'QPolygon') { if (obj.type === 'QPolygon') {
obj.set({ stroke: 'black' }) obj.set({ stroke: 'black' })
@ -238,7 +236,7 @@ export function useCanvasEvent() {
setCurrentObject(target) setCurrentObject(target)
const { selected, deselected } = e const { selected, deselected } = e
if (deselected.length > 0) { if (deselected?.length > 0) {
deselected.forEach((obj) => { deselected.forEach((obj) => {
if (obj.type === 'QPolygon') { if (obj.type === 'QPolygon') {
obj.set({ stroke: 'black' }) obj.set({ stroke: 'black' })
@ -246,7 +244,7 @@ export function useCanvasEvent() {
}) })
} }
if (selected.length > 0) { if (selected?.length > 0) {
selected.forEach((obj) => { selected.forEach((obj) => {
if (obj.type === 'QPolygon') { if (obj.type === 'QPolygon') {
obj.set({ stroke: 'red' }) obj.set({ stroke: 'red' })

View File

@ -51,7 +51,7 @@ export function useContextMenu() {
const [column, setColumn] = useState(null) const [column, setColumn] = useState(null)
const { handleZoomClear } = useCanvasEvent() const { handleZoomClear } = useCanvasEvent()
const { moveObjectBatch } = useObjectBatch({}) const { moveObjectBatch } = useObjectBatch({})
const { moveSurfaceShapeBatch } = useSurfaceShapeBatch() const { moveSurfaceShapeBatch, surfaceShapeActualSize } = useSurfaceShapeBatch()
const currentMenuSetting = () => { const currentMenuSetting = () => {
switch (currentMenu) { switch (currentMenu) {
case MENU.PLAN_DRAWING: case MENU.PLAN_DRAWING:
@ -176,6 +176,7 @@ export function useContextMenu() {
{ {
id: 'sizeEdit', id: 'sizeEdit',
name: getMessage('contextmenu.size.edit'), name: getMessage('contextmenu.size.edit'),
component: <SizeSetting id={popupId} target={currentObject} />,
}, },
{ {
id: 'remove', id: 'remove',
@ -211,6 +212,7 @@ export function useContextMenu() {
{ {
id: 'flowDirectionEdit', id: 'flowDirectionEdit',
name: getMessage('contextmenu.flow.direction.edit'), name: getMessage('contextmenu.flow.direction.edit'),
component: <FlowDirectionSetting id={popupId} target={currentObject} />,
}, },
], ],
]) ])
@ -257,7 +259,6 @@ export function useContextMenu() {
}, [currentContextMenu]) }, [currentContextMenu])
useEffect(() => { useEffect(() => {
console.log('currentObject', currentObject)
if (currentObject?.name) { if (currentObject?.name) {
switch (currentObject.name) { switch (currentObject.name) {
case 'triangleDormer': case 'triangleDormer':
@ -303,6 +304,11 @@ export function useContextMenu() {
case 'roof': case 'roof':
setContextMenu([ setContextMenu([
[ [
{
id: 'surfaceShapeActualSize',
name: '면형상 실측',
fn: () => surfaceShapeActualSize(),
},
{ {
id: 'sizeEdit', id: 'sizeEdit',
name: '사이즈 변경', name: '사이즈 변경',
@ -336,7 +342,7 @@ export function useContextMenu() {
{ {
id: 'linePropertyEdit', id: 'linePropertyEdit',
name: getMessage('contextmenu.line.property.edit'), name: getMessage('contextmenu.line.property.edit'),
component: <LinePropertySetting id={popupId} />, component: <LinePropertySetting id={popupId} target={currentObject} />,
}, },
{ {
id: 'flowDirectionEdit', id: 'flowDirectionEdit',

View File

@ -1515,9 +1515,17 @@ export function useMode() {
pitch: 4, pitch: 4,
sleeve: true, sleeve: true,
} }
/*if (index === 1 || index === 3) { /*if (index === 1) {
line.attributes = { 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, //출폭 offset: 50, //출폭
width: 30, //폭 width: 30, //폭
pitch: 4, //구배 pitch: 4, //구배
@ -1525,8 +1533,8 @@ export function useMode() {
} }
} else { } else {
line.attributes = { line.attributes = {
type: LINE_TYPE.WALLLINE.EAVES, type: LINE_TYPE.WALLLINE.GABLE,
offset: 40, offset: 20,
width: 50, width: 50,
pitch: 4, pitch: 4,
sleeve: true, sleeve: true,
@ -1746,12 +1754,20 @@ export function useMode() {
return { x1: point.x, y1: point.y } return { x1: point.x, y1: point.y }
}), }),
) )
if (wall.direction) {
roof.direction = wall.direction
}
roof.name = POLYGON_TYPE.ROOF roof.name = POLYGON_TYPE.ROOF
roof.setWall(wall) roof.setWall(wall)
roof.lines.forEach((line, index) => { 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 = { line.attributes = {
roofId: roof.id, roofId: roof.id,
planeSize: lineLength,
actualSize: lineLength,
wallLine: wall.lines[index].id, wallLine: wall.lines[index].id,
type: wall.lines[index].attributes.type, type: wall.lines[index].attributes.type,
offset: wall.lines[index].attributes.offset, offset: wall.lines[index].attributes.offset,
@ -1766,13 +1782,15 @@ export function useMode() {
} }
wall.lines.forEach((line, index) => { 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.roofId = roof.id
line.attributes.currentRoof = roof.lines[index].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) setRoof(roof)
setWall(wall) setWall(wall)

View File

@ -187,6 +187,8 @@ export function usePlan() {
userId: item.userId, userId: item.userId,
canvasStatus: dbToCanvasFormat(item.canvasStatus), canvasStatus: dbToCanvasFormat(item.canvasStatus),
isCurrent: false, isCurrent: false,
bgImageName: item.bgImageName,
mapPositionAddress: item.mapPositionAddress,
})), })),
) )
} }

View File

@ -1,11 +1,13 @@
import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom' import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom'
import { useRecoilValue } from 'recoil' import { useRecoilValue } from 'recoil'
import { fabric } from 'fabric' 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 { QPolygon } from '@/components/fabric/QPolygon'
import { isSamePoint } from '@/util/qpolygon-utils' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils'
import { flowDisplaySelector } from '@/store/settingAtom' import { flowDisplaySelector } from '@/store/settingAtom'
import { fontSelector } from '@/store/fontAtom' import { fontSelector } from '@/store/fontAtom'
import { QLine } from '@/components/fabric/QLine'
import { POLYGON_TYPE } from '@/common/common'
export const usePolygon = () => { export const usePolygon = () => {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -25,6 +27,7 @@ export const usePolygon = () => {
}) })
canvas?.add(polygon) canvas?.add(polygon)
addLengthText(polygon)
return polygon return polygon
} }
@ -40,7 +43,64 @@ export const usePolygon = () => {
} }
const addLengthText = (polygon) => { 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) => { points.forEach((start, i) => {
const end = points[(i + 1) % points.length] const end = points[(i + 1) % points.length]
const dx = end.x - start.x const dx = end.x - start.x
@ -71,12 +131,12 @@ export const usePolygon = () => {
lockScalingY: true, lockScalingY: true,
idx: i, idx: i,
name: 'lengthText', name: 'lengthText',
parent: this, parent: polygon,
}) })
// this.texts.push(text) // this.texts.push(text)
canvas.add(text) canvas.add(text)
}) })*/
canvas.renderAll() canvas.renderAll()
} }
@ -409,7 +469,8 @@ export const usePolygon = () => {
const addTextByArrows = (arrows, txt, canvas) => { const addTextByArrows = (arrows, txt, canvas) => {
arrows.forEach((arrow, index) => { 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}`, { const text = new fabric.Text(`${textStr}`, {
fontSize: flowFontOptions.fontSize.value, 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 { return {
addPolygon, addPolygon,
addPolygonByLines, addPolygonByLines,
removePolygon, removePolygon,
drawDirectionArrow, drawDirectionArrow,
addLengthText,
splitPolygonWithLines,
} }
} }

View File

@ -285,6 +285,12 @@
"modal.flow.direction.setting": "流れ方向の設定", "modal.flow.direction.setting": "流れ方向の設定",
"modal.flow.direction.setting.info": "流れ方向を選択してください。", "modal.flow.direction.setting.info": "流れ方向を選択してください。",
"modal.image.size.setting": "画像のサイズ変更", "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.save": "PLAN을 저장하시겠습니까?",
"plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?",
"plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?",
@ -818,7 +824,7 @@
"estimate.detail.estimateType": "注文分類", "estimate.detail.estimateType": "注文分類",
"estimate.detail.roofCns": "屋根材・仕様施工", "estimate.detail.roofCns": "屋根材・仕様施工",
"estimate.detail.remarks": "備考", "estimate.detail.remarks": "備考",
"estimate.detail.nextSubmit": "後日資料提出", "estimate.detail.fileFlg": "後日資料提出",
"estimate.detail.header.fileList1": "ファイル添付", "estimate.detail.header.fileList1": "ファイル添付",
"estimate.detail.fileList.btn": "ファイル選択", "estimate.detail.fileList.btn": "ファイル選択",
"estimate.detail.header.fileList2": "添付ファイル一覧", "estimate.detail.header.fileList2": "添付ファイル一覧",
@ -835,11 +841,62 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000", "estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)", "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
"estimate.detail.header.showPrice": "価格表示", "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.description1": "製品価格 OPEN",
"estimate.detail.showPrice.description2": "追加, 変更資材", "estimate.detail.showPrice.description2": "追加, 変更資材",
"estimate.detail.showPrice.description3": "添付必須", "estimate.detail.showPrice.description3": "添付必須",
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する", "estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
"estimate.detail.showPrice.btn2": "製品を追加", "estimate.detail.showPrice.addItem": "製品を追加",
"estimate.detail.showPrice.btn3": "製品削除" "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": "シミュレーション案内事項"
} }

View File

@ -290,6 +290,12 @@
"modal.flow.direction.setting": "흐름 방향 설정", "modal.flow.direction.setting": "흐름 방향 설정",
"modal.flow.direction.setting.info": "흐름방향을 선택하세요.", "modal.flow.direction.setting.info": "흐름방향을 선택하세요.",
"modal.image.size.setting": "이미지 크기 조절", "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.save": "PLAN을 저장하시겠습니까?",
"plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?",
"plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?",
@ -394,6 +400,9 @@
"modal.module.circuit.number.edit": "모듈 일괄 회로 번호 변경", "modal.module.circuit.number.edit": "모듈 일괄 회로 번호 변경",
"modal.module.circuit.number.edit.info": "회로 번호를 입력해주세요.", "modal.module.circuit.number.edit.info": "회로 번호를 입력해주세요.",
"modal.module.circuit.number": "회로 번호", "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.data": "No data",
"common.message.no.dataDown": "No data to download", "common.message.no.dataDown": "No data to download",
"common.message.noData": "No data to display", "common.message.noData": "No data to display",
@ -489,6 +498,7 @@
"commons.east": "동", "commons.east": "동",
"commons.south": "남", "commons.south": "남",
"commons.north": "북", "commons.north": "북",
"commons.none": "선택안함",
"font.style.normal": "보통", "font.style.normal": "보통",
"font.style.italic": "기울임꼴", "font.style.italic": "기울임꼴",
"font.style.bold": "굵게", "font.style.bold": "굵게",
@ -824,7 +834,7 @@
"estimate.detail.estimateType": "주문분류", "estimate.detail.estimateType": "주문분류",
"estimate.detail.roofCns": "지붕재・사양시공", "estimate.detail.roofCns": "지붕재・사양시공",
"estimate.detail.remarks": "비고", "estimate.detail.remarks": "비고",
"estimate.detail.nextSubmit": "후일자료제출", "estimate.detail.fileFlg": "후일자료제출",
"estimate.detail.header.fileList1": "파일첨부", "estimate.detail.header.fileList1": "파일첨부",
"estimate.detail.fileList.btn": "파일선택", "estimate.detail.fileList.btn": "파일선택",
"estimate.detail.header.fileList2": "첨부파일 목록", "estimate.detail.header.fileList2": "첨부파일 목록",
@ -841,11 +851,62 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100", "estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)", "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
"estimate.detail.header.showPrice": "가격표시", "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.description1": "제품 가격 OPEN",
"estimate.detail.showPrice.description2": "추가, 변경 자재", "estimate.detail.showPrice.description2": "추가, 변경 자재",
"estimate.detail.showPrice.description3": "첨부필수", "estimate.detail.showPrice.description3": "첨부필수",
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인", "estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
"estimate.detail.showPrice.btn2": "제품추가", "estimate.detail.showPrice.addItem": "제품추가",
"estimate.detail.showPrice.btn3": "제품삭제" "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": "시뮬레이션 안내사항"
} }

View File

@ -185,6 +185,7 @@ export const corridorDimensionSelector = selector({
const settingModalFirstOptions = get(settingModalFirstOptionsState) const settingModalFirstOptions = get(settingModalFirstOptionsState)
return settingModalFirstOptions.dimensionDisplay.find((option) => option.selected) return settingModalFirstOptions.dimensionDisplay.find((option) => option.selected)
}, },
dangerouslyAllowMutability: true,
}) })
// 디스플레이 설정 - 화면 표시 // 디스플레이 설정 - 화면 표시

View File

@ -925,3 +925,32 @@ export function checkLineOrientation(line) {
return 'diagonal' // 대각선 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
}

View File

@ -1,13 +1,6 @@
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util'
calculateIntersection,
distanceBetweenPoints,
findClosestPoint,
getDegreeByChon,
getDirectionByPoint,
isPointOnLine,
} from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
@ -1177,244 +1170,6 @@ export default function offsetPolygon(vertices, offset) {
polygon.canvas.renderAll() 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) { function normalizePoint(point) {
return { return {
@ -1432,7 +1187,7 @@ function arePolygonsEqual(polygon1, polygon2) {
return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index])) return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index]))
} }
function removeDuplicatePolygons(polygons) { export function removeDuplicatePolygons(polygons) {
const uniquePolygons = [] const uniquePolygons = []
polygons.forEach((polygon) => { polygons.forEach((polygon) => {
@ -1477,6 +1232,42 @@ function calculateAngleBetweenLines(line1, line2) {
return (angleInRadians * 180) / Math.PI 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) => { export const drawRidgeRoof = (roofId, canvas) => {
const roof = canvas?.getObjects().find((object) => object.id === roofId) const roof = canvas?.getObjects().find((object) => object.id === roofId)
const hasNonParallelLines = roof.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) 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] 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] nextRoof = index === wallLines.length - 1 ? wallLines[0] : index === wallLines.length ? wallLines[1] : wallLines[index + 1]
if (prevRoof.direction !== nextRoof.direction && currentWall.length <= currentRoof.length) { const angle1 = calculateAngle(prevRoof.startPoint, prevRoof.endPoint)
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.length }) 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) ridgeRoof.sort((a, b) => a.length - b.length)
console.log('ridgeRoof', ridgeRoof)
ridgeRoof.forEach((item) => { ridgeRoof.forEach((item) => {
if (getMaxRidge(roofLines.length) > roof.ridges.length) { if (getMaxRidge(roofLines.length) > roof.ridges.length) {
let index = item.index, 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가 같은 내부선 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가 같은 내부선 yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선
let ridgeBaseLength = Math.round((currentRoof.length / 2) * 10) / 10, // 지붕의 기반 길이 let ridgeBaseLength = Math.round(currentRoof.attributes.planeSize / 2), // 지붕의 기반 길이
ridgeMaxLength = Math.min(prevRoof.length, nextRoof.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이 ridgeMaxLength = Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
ridgeAcrossLength = Math.round((ridgeMaxLength - currentRoof.length) * 10) / 10 // 맞은편 벽까지의 길이 - 지붕의 기반 길이 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 let acrossRoof = anotherRoof
.filter((roof) => { .filter((roof) => {
if (roof.x1 === roof.x2) { const angle1 = calculateAngle(currentRoof.startPoint, currentRoof.endPoint)
if ((nextRoof.direction === 'right' && roof.x1 > currentRoof.x1) || (nextRoof.direction === 'left' && roof.x1 < currentRoof.x1)) { const angle2 = calculateAngle(roof.startPoint, roof.endPoint)
return roof if (Math.abs(angle1 - angle2) === 180) {
} return roof
}
if (roof.y1 === roof.y2) {
if ((nextRoof.direction === 'top' && roof.y1 < currentRoof.y1) || (nextRoof.direction === 'bottom' && roof.y1 > currentRoof.y1)) {
return roof
}
} }
}) })
.reduce((prev, current) => { .reduce((prev, current) => {
let hasBetweenRoof = false let hasBetweenRoof = false
if (current.x1 === current.x2) { if (current.x1 === current.x2) {
hasBetweenRoof = roofLines hasBetweenRoof = roofLines
.filter((roof) => roof !== current && roof !== currentRoof) .filter((roof) => roof !== current)
.some((line) => { .some((line) => {
let currentY2 = currentRoof.y2 let currentY2 = currentRoof.y2
if (yEqualInnerLines.length > 0) { 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 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 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) const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1)
return isY1Between && isY2Between && isX1Between && isX2Between return isY1Between && isY2Between && isX1Between && isX2Between
}) })
} }
if (current.y1 === current.y2) { if (current.y1 === current.y2) {
hasBetweenRoof = wallLines hasBetweenRoof = roofLines
.filter((roof) => roof !== current && roof !== currentRoof) .filter((roof) => roof !== current)
.some((line) => { .some((line) => {
let currentX2 = currentRoof.x2 let currentX2 = currentRoof.x2
if (xEqualInnerLines.length > 0) { if (xEqualInnerLines.length > 0) {
@ -1614,20 +1407,25 @@ const drawRidge = (roof, canvas) => {
} }
} }
}, undefined) }, undefined)
if (acrossRoof !== undefined) { if (acrossRoof !== undefined) {
if (currentRoof.x1 === currentRoof.x2) { if (currentRoof.x1 === currentRoof.x2) {
if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) { 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 (currentRoof.y1 === currentRoof.y2) {
if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) { 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) { if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) {
let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
if (currentRoof.x1 === currentRoof.x2) { if (currentRoof.x1 === currentRoof.x2) {
@ -1769,6 +1567,9 @@ const drawRidge = (roof, canvas) => {
attributes: { roofId: roof.id }, 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) canvas.add(ridge)
roof.ridges.push(ridge) roof.ridges.push(ridge)
roof.innerLines.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.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) => !(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)) 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) canvas.add(newRidge)
roof.ridges.push(newRidge) roof.ridges.push(newRidge)
roof.innerLines.push(newRidge) roof.innerLines.push(newRidge)
@ -1884,7 +1687,7 @@ const drawHips = (roof, canvas) => {
stroke: 'red', stroke: 'red',
strokeWidth: 1, strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP, name: LINE_TYPE.SUBLINE.HIP,
attributes: { roofId: roof.id, currentRoof: currentRoof.id }, attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
}) })
canvas.add(hip1) canvas.add(hip1)
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10 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', stroke: 'red',
strokeWidth: 1, strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP, name: LINE_TYPE.SUBLINE.HIP,
attributes: { attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
roofId: roof.id,
currentRoof: currentRoof.id,
planeSize: currentRoof.length,
actualSize: currentRoof.length,
},
}) })
canvas.add(hip2) canvas.add(hip2)
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10 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', stroke: 'red',
strokeWidth: 1, strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP, name: LINE_TYPE.SUBLINE.HIP,
attributes: { attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
roofId: roof.id,
currentRoof: currentRoof.id,
planeSize: currentRoof.length,
actualSize: currentRoof.length,
},
}) })
canvas.add(hip) canvas.add(hip)
const hipBase = ((Math.abs(hip.x1 - hip.x2) + Math.abs(hip.y1 - hip.y2)) / 2) * 10 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 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) { if (prevDegree === currentDegree) {
hip.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip.attributes.planeSize, 2) + Math.pow(hipHeight, 2))) 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 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 이상 이어지지 않은 라인 포인트 계산 3 이상 이어지지 않은 라인 포인트 계산
모임지붕에서 point는 3 이상의 라인과 접해야 . 모임지붕에서 point는 3 이상의 라인과 접해야 .
@ -2216,6 +1923,8 @@ const connectLinePoint = (polygon) => {
stroke: 'purple', stroke: 'purple',
strokeWidth: 1, 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.canvas.add(line)
polygon.innerLines.push(line) polygon.innerLines.push(line)
}) })
@ -2272,6 +1981,8 @@ const connectLinePoint = (polygon) => {
stroke: 'purple', stroke: 'purple',
strokeWidth: 1, 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.canvas.add(line)
polygon.innerLines.push(line) polygon.innerLines.push(line)
}) })
@ -2576,6 +2287,8 @@ const changeEavesRoof = (currentRoof, canvas) => {
hipX2 = midX - addHipX2 hipX2 = midX - addHipX2
hipY2 = midY - addHipY2 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) => { hipLines.forEach((hip) => {
@ -2753,6 +2466,8 @@ const changeGableRoof = (currentRoof, canvas) => {
}) })
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } 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], { let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX, midY], {
fontSize: roof.fontSize, fontSize: roof.fontSize,
@ -2814,6 +2529,14 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
if (wallLine.length > 0) { if (wallLine.length > 0) {
wallLine = wallLine[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 midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심 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 } 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], { const hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX + hipX2, midY + hipY2], {
@ -2937,10 +2662,15 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoof: currentRoof.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) canvas?.add(hip1)
roof.innerLines.push(hip1) roof.innerLines.push(hip1)
@ -2956,6 +2686,10 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
actualSize: currentRoof.length, 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) canvas?.add(hip2)
roof.innerLines.push(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], { const gableLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], {
fontSize: roof.fontSize, fontSize: roof.fontSize,
stroke: 'red', stroke: 'red',
@ -2983,10 +2717,15 @@ const changeHipAndGableRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoof: currentRoof.id, currentRoof: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
actualSize: currentRoof.length,
}, },
}) })
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) canvas?.add(gableLine)
roof.innerLines.push(gableLine) roof.innerLines.push(gableLine)
}) })
@ -3016,6 +2755,18 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
wallLine = wallLine[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 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 midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심 const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
const midWallX = (wallLine.x1 + wallLine.x2) / 2 // 벽의 X 중심 const midWallX = (wallLine.x1 + wallLine.x2) / 2 // 벽의 X 중심
@ -3137,6 +2888,8 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
hipX2 = midX - xWidth hipX2 = midX - xWidth
hipY2 = midY - yWidth 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 let hipX1 = (Math.sign(currentRoof.x1 - midX) * currentRoof.attributes.width) / 2
@ -3150,10 +2903,14 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoofId: currentRoof.id, currentRoofId: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
actualSize: currentRoof.length,
}, },
}) })
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) canvas?.add(gable1)
roof.innerLines.push(gable1) roof.innerLines.push(gable1)
@ -3168,10 +2925,13 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoofId: currentRoof.id, currentRoofId: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
actualSize: currentRoof.length,
}, },
}) })
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) canvas?.add(gable2)
roof.innerLines.push(gable2) roof.innerLines.push(gable2)
@ -3183,10 +2943,11 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoofId: currentRoof.id, currentRoofId: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
actualSize: currentRoof.length,
}, },
}) })
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) canvas?.add(gable3)
roof.innerLines.push(gable3) roof.innerLines.push(gable3)
@ -3198,10 +2959,13 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoofId: currentRoof.id, currentRoofId: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
actualSize: currentRoof.length,
}, },
}) })
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) canvas?.add(hip1)
roof.innerLines.push(hip1) roof.innerLines.push(hip1)
@ -3213,10 +2977,13 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoofId: currentRoof.id, currentRoofId: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
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) canvas?.add(hip2)
roof.innerLines.push(hip2) roof.innerLines.push(hip2)
} }
@ -3312,6 +3079,9 @@ const changeWallRoof = (currentRoof, canvas) => {
canvas?.remove(line) 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) { 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 prevSignX = Math.sign(prevRoof.x1 - prevRoof.x2)
const prevSignY = Math.sign(prevRoof.y1 - prevRoof.y2) const prevSignY = Math.sign(prevRoof.y1 - prevRoof.y2)
@ -3417,6 +3187,8 @@ const changeWallRoof = (currentRoof, canvas) => {
y2: ridge.y2 - diffY, 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], { let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX, wallMidY], {
fontSize: roof.fontSize, fontSize: roof.fontSize,
@ -3426,10 +3198,14 @@ const changeWallRoof = (currentRoof, canvas) => {
attributes: { attributes: {
roofId: roof.id, roofId: roof.id,
currentRoofId: currentRoof.id, currentRoofId: currentRoof.id,
planeSize: currentRoof.length, actualSize: 0,
actualSize: currentRoof.length,
}, },
}) })
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], { let hip2 = new QLine([currentRoof.x2, currentRoof.y2, wallMidX, wallMidY], {
fontSize: roof.fontSize, fontSize: roof.fontSize,
stroke: 'red', stroke: 'red',
@ -3442,6 +3218,10 @@ const changeWallRoof = (currentRoof, canvas) => {
actualSize: currentRoof.length, 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(hip1)
canvas?.add(hip2) canvas?.add(hip2)
roof.innerLines.push(hip1) roof.innerLines.push(hip1)
@ -3505,8 +3285,13 @@ export const changeCurrentRoof = (currentRoof, canvas) => {
newRoof.setWall(wall) newRoof.setWall(wall)
newRoof.lines.forEach((line, index) => { 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 = { line.attributes = {
roofId: newRoof.id, roofId: newRoof.id,
planeSize: lineLength,
actualSize: lineLength,
wallLine: wall.lines[index].id, wallLine: wall.lines[index].id,
type: wall.lines[index].attributes.type, type: wall.lines[index].attributes.type,
offset: wall.lines[index].attributes.offset, offset: wall.lines[index].attributes.offset,
@ -3561,6 +3346,19 @@ const reDrawPolygon = (polygon, canvas) => {
line.attributes = l.attributes 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) canvas?.add(newPolygon)

View File

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

View File

@ -527,6 +527,11 @@
resolved "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz" resolved "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz"
integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA== 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": "@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" 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" escape-string-regexp "^1.0.5"
supports-color "^5.3.0" 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: "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.6.0" version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
@ -5832,6 +5844,11 @@ rbush@^3.0.1:
dependencies: dependencies:
quickselect "^2.0.0" 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: react-color-palette@^7.2.2:
version "7.2.2" version "7.2.2"
resolved "https://registry.npmjs.org/react-color-palette/-/react-color-palette-7.2.2.tgz" resolved "https://registry.npmjs.org/react-color-palette/-/react-color-palette-7.2.2.tgz"