Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
minsik 2024-12-06 10:23:45 +09:00
commit 50d92065c0
23 changed files with 1257 additions and 521 deletions

View File

@ -2,8 +2,6 @@
import { correntObjectNoState } from '@/store/settingAtom'
import { notFound, usePathname, useSearchParams } from 'next/navigation'
// import { ErrorBoundary } from 'next/dist/client/components/error-boundary'
// import ServerError from '../error'
import { createContext, useReducer, useState } from 'react'
import { useSetRecoilState } from 'recoil'

View File

@ -3,15 +3,23 @@
import FloorPlanProvider from './FloorPlanProvider'
import FloorPlan from '@/components/floor-plan/FloorPlan'
import CanvasLayout from '@/components/floor-plan/CanvasLayout'
import { usePathname } from 'next/navigation'
export default function FloorPlanLayout({ children }) {
console.log('🚀 ~ FloorPlanLayout ~ FloorPlanLayout:')
const pathname = usePathname()
console.log('🚀 ~ FloorPlanLayout ~ pathname:', pathname)
return (
<>
<FloorPlanProvider>
<FloorPlan>
<CanvasLayout>{children}</CanvasLayout>
{pathname.includes('estimate') || pathname.includes('simulator') ? (
<div className="canvas-layout">{children}</div>
) : (
<CanvasLayout>{children}</CanvasLayout>
)}
{/* <CanvasLayout>{children}</CanvasLayout> */}
</FloorPlan>
</FloorPlanProvider>
</>

View File

@ -0,0 +1,18 @@
import React from 'react'
import StuffSubHeader from '@/components/management/StuffSubHeader'
import '@/styles/contents.scss'
import StuffDetail from '@/components/management/StuffDetail'
export default function ManagementStuffRegPage() {
return (
<>
<StuffSubHeader type={'temp'} />
<div className="sub-content">
<div className="sub-content-inner">
<div className="sub-content-box">
<StuffDetail />
</div>
</div>
</div>
</>
)
}

View File

@ -1,7 +1,7 @@
'use client'
import { useEffect, useState, useContext } from 'react'
import { useRecoilValue } from 'recoil'
import { useRecoilValue, useSetRecoilState } from 'recoil'
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { useMessage } from '@/hooks/useMessage'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
@ -9,7 +9,7 @@ import SingleDatePicker from '../common/datepicker/SingleDatePicker'
import EstimateFileUploader from './EstimateFileUploader'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import { isEmptyArray, isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import dayjs from 'dayjs'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
@ -18,6 +18,8 @@ import Select, { components } from 'react-select'
import { convertNumberToPriceDecimal, convertNumberToPriceDecimalToFixed } from '@/util/common-utils'
import ProductFeaturesPop from './popup/ProductFeaturesPop'
import { v4 as uuidv4 } from 'uuid'
import { correntObjectNoState } from '@/store/settingAtom'
import { useSearchParams } from 'next/navigation'
export default function Estimate({ params }) {
const [uniqueData, setUniqueData] = useState([])
@ -74,6 +76,15 @@ export default function Estimate({ params }) {
const { setMenuNumber } = useCanvasMenu()
/**
* objectNo 셋팅
* url로 넘어온 objectNo을 리코일에 세팅
*/
const setCurrentObjectNo = useSetRecoilState(correntObjectNoState)
const searchParams = useSearchParams()
const currentObjectNo = searchParams.get('objectNo')
setCurrentObjectNo(currentObjectNo)
// props
const fileUploadProps = {
uploadFiles: files,
@ -120,7 +131,6 @@ export default function Estimate({ params }) {
let url = `/api/estimate/special-note-title-list`
get({ url: url }).then((res) => {
if (isNotEmptyArray(res)) {
// ATTR001ATTR002ATTR003ATTR007ATTR009ATTR010ATTR015ATTR019
if (estimateContextState?.estimateOption) {
res.map((row) => {
let estimateOption = estimateContextState?.estimateOption?.split('、')
@ -140,10 +150,10 @@ export default function Estimate({ params }) {
//detail
//ATTR003,ATTR007
if (row.code === 'ATTR003') {
row.check = true
//row.check = true
}
if (row.code === 'ATTR007') {
row.check = true
//row.check = true
}
})
@ -191,9 +201,8 @@ export default function Estimate({ params }) {
}, [specialNoteList])
// remark
const settingShowContent = (code, event) => {
const settingShowContent = (code) => {
setShowContentCode(code)
event.stopPropagation()
}
// estimateContextState
@ -203,49 +212,62 @@ export default function Estimate({ params }) {
files.map((row) => {
fileList.push(row.data)
setEstimateContextState({ fileList: row.data, newFileList: fileList })
// setEstimateContextState({ fileList: row.data })
})
} else {
setEstimateContextState({ fileList: [] })
setEstimateContextState({ fileList: [], newFileList: [] })
}
}, [files])
useEffect(() => {
if (originFiles.length > 0) {
setEstimateContextState({
originFiles: originFiles,
})
}
}, [originFiles])
// set
useEffect(() => {
if (isNotEmptyArray(estimateContextState.fileList)) {
//
setFiles([])
setOriginFiles(estimateContextState.fileList)
} else {
if (originFiles.length > 0) {
if (isEmptyArray(files)) {
let file
file = originFiles.filter((item) => item.delFlg === '0')
setEstimateContextState({
originFiles: file,
})
setOriginFiles(file)
}
}
}
}, [estimateContextState?.fileList])
//
const returnOriginFile = (no) => {
originFiles.map((file) => {
if (file.no === no) {
file.delFlg = '0'
}
})
setOriginFiles((prev) => {
return [...prev]
})
setEstimateContextState({
originFiles: originFiles,
})
}
// ( ?)
const deleteOriginFile = async (objectNo, no) => {
const delParams = {
userId: session.userId,
objectNo: objectNo,
no: no,
}
alert(getMessage('estimate.detail.alert.delFile'))
// await promisePost({ url: 'api/file/fileDelete', data: delParams }).then((res) => {
// if (res.status === 204) {
// setOriginFiles(originFiles.filter((file) => file.objectNo === objectNo && file.no !== no))
// setEstimateContextState({
// fileList: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no),
// originFiles: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no),
// newFileList: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no),
// })
const deleteOriginFile = (no) => {
originFiles.map((file) => {
if (file.no === no) {
file.delFlg = '1'
}
})
// alert(getMessage('plan.message.delete'))
// }
// })
setOriginFiles((prev) => {
return [...prev]
})
setEstimateContextState({
originFiles: originFiles,
})
alert(getMessage('estimate.detail.alert.delFile'))
}
// option &&
@ -408,24 +430,26 @@ export default function Estimate({ params }) {
if (isNotEmptyArray(data.data2)) {
estimateContextState.itemList.map((item) => {
let checkYn = false
data.data2.map((item2) => {
if (item2) {
if (item2.itemId === item.itemId) {
for (let i = 0; i < data.data2.length; i++) {
if (data.data2[i]) {
if (data.data2[i].itemId === item.itemId) {
updateList.push({
...item,
openFlg: item2.unitPrice === '0.0' ? '1' : '0',
salePrice: item2.unitPrice === null ? '0' : item2.unitPrice,
saleTotPrice: (item.amount * item2.unitPrice).toString(),
openFlg: data.data2[i].unitPrice === '0.0' ? '1' : '0',
salePrice: data.data2[i].unitPrice === null ? '0' : data.data2[i].unitPrice,
saleTotPrice: (item.amount * data.data2[i].unitPrice).toString(),
})
checkYn = true
break
}
}
})
}
if (!checkYn) {
updateList.push({ ...item, salePrice: '0', saleTotPrice: '0' })
}
})
setEstimateContextState({
priceCd: showPriceCd,
itemList: updateList,
@ -472,7 +496,7 @@ export default function Estimate({ params }) {
//PKG input
const onChangePkgAsp = (value) => {
if (estimateContextState.estimateType === 'YJSS') {
let pkgAsp = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
let pkgAsp = Number(value.replace(/[^-\.0-9]/g, '').replaceAll(',', ''))
if (isNaN(pkgAsp)) {
pkgAsp = 0
} else {
@ -485,7 +509,7 @@ export default function Estimate({ params }) {
setEstimateContextState({
pkgAsp: pkgAsp,
pkgTotPrice: pkgTotPrice.toFixed(3),
pkgTotPrice: pkgTotPrice.toFixed(2),
})
//
setItemChangeYn(true)
@ -496,6 +520,7 @@ export default function Estimate({ params }) {
const onChangeAmount = (value, dispOrder, index) => {
//itemChangeFlg = 1, partAdd = 0
let amount = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
if (isNaN(amount)) {
amount = '0'
} else {
@ -584,14 +609,12 @@ export default function Estimate({ params }) {
updates.pkgMaterialFlg = res.pkgMaterialFlg
updates.pnowW = res.pnowW
updates.salePrice = res.salePrice
// updates.salePrice = ''
updates.specification = res.specification
updates.unit = res.unit
updates.specialNoteCd = res.spnAttrCds
updates.itemGroup = res.itemGroup
updates.delFlg = '0' // 0
updates.saleTotPrice = (res.salePrice * estimateContextState.itemList[index].amount).toString()
// updates.saleTotPrice = ''
updates.amount = ''
updates.openFlg = res.openFlg
@ -622,7 +645,6 @@ export default function Estimate({ params }) {
}
}
} else if (item.paDispOrder === dispOrder) {
//
return { ...item, delFlg: '1' }
} else {
return item
@ -639,12 +661,14 @@ export default function Estimate({ params }) {
bomItem.salePrice = '0'
bomItem.saleTotPrice = '0'
bomItem.unitPrice = '0'
bomItem.showSalePrice = '0'
} else {
bomItem.dispOrder = (index + 1 + Number(dispOrder)).toString()
bomItem.paDispOrder = dispOrder
bomItem.salePrice = '0'
bomItem.saleTotPrice = '0'
bomItem.unitPrice = '0'
bomItem.showSalePrice = '0'
}
bomItem.delFlg = '0'
@ -653,6 +677,7 @@ export default function Estimate({ params }) {
bomItem.addFlg = true // addFlg
})
updateList = updateList.filter((item) => item.delFlg === '0')
setEstimateContextState({
itemList: [...updateList, ...bomList],
})
@ -729,15 +754,24 @@ export default function Estimate({ params }) {
delete item.showSaleTotPrice
if (item.delFlg === '0') {
let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0
let price = Number(item.saleTotPrice?.replaceAll(',', '')) || 0
let price
if (amount === 0) {
price = 0
} else {
price = Number(item.saleTotPrice?.replaceAll(',', '')) || 0
}
if (item.moduleFlg === '1') {
const volKw = (item.pnowW * amount) / 1000
totals.totVolKw += volKw
}
totals.totAmount += amount
totals.supplyPrice += price
totals.totAmount += amount
if (item.paDispOrder) {
item.showSalePrice = '0'
item.showSaleTotPrice = '0'
}
if (item.openFlg === '1') {
item.showSalePrice = '0'
item.showSaleTotPrice = '0'
@ -755,12 +789,16 @@ export default function Estimate({ params }) {
itemList.forEach((item) => {
if (item.delFlg === '0') {
let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0
let salePrice = Number(item.salePrice?.replaceAll(',', '')) || 0
let salePrice
if (item.moduleFlg === '1') {
const volKw = (item.pnowW * amount) / 1000
totals.totVolKw += volKw
}
if (amount === 0) {
salePrice = 0
} else {
salePrice = Number(item.salePrice?.replaceAll(',', '')) || 0
}
totals.totAmount += amount
if (item.pkgMaterialFlg === '1') {
@ -773,6 +811,14 @@ export default function Estimate({ params }) {
item.showSalePrice = '0'
item.showSaleTotPrice = '0'
}
} else {
item.showSalePrice = '0'
item.showSaleTotPrice = '0'
}
if (item.openFlg === '1') {
item.showSalePrice = '0'
item.showSaleTotPrice = '0'
}
}
})
@ -782,25 +828,24 @@ export default function Estimate({ params }) {
totals.vatPrice = totals.supplyPrice * 0.1
totals.totPrice = totals.supplyPrice + totals.vatPrice
}
if (estimateContextState.estimateType === 'YJOD') {
calculateYJODTotals(estimateContextState.itemList)
setEstimateContextState({
totAmount: totals.totAmount,
totVolKw: totals.totVolKw.toFixed(3),
supplyPrice: totals.supplyPrice.toFixed(3),
vatPrice: totals.vatPrice.toFixed(3),
totPrice: totals.totPrice.toFixed(3),
totVolKw: totals.totVolKw.toFixed(2),
supplyPrice: totals.supplyPrice.toFixed(0), //
vatPrice: totals.vatPrice.toFixed(0), //
totPrice: totals.totPrice.toFixed(0), //
})
} else if (estimateContextState.estimateType === 'YJSS') {
calculateYJSSTotals(estimateContextState.itemList)
setEstimateContextState({
pkgTotPrice: totals.pkgTotPrice,
totAmount: totals.totAmount,
totVolKw: totals.totVolKw.toFixed(3),
supplyPrice: totals.supplyPrice.toFixed(3),
vatPrice: totals.vatPrice.toFixed(3),
totPrice: totals.totPrice.toFixed(3),
totVolKw: totals.totVolKw.toFixed(2),
supplyPrice: totals.supplyPrice.toFixed(0), //
vatPrice: totals.vatPrice.toFixed(0), //
totPrice: totals.totPrice.toFixed(0), //
})
}
@ -1014,16 +1059,14 @@ export default function Estimate({ params }) {
let constructSpecificationMulti = estimateContextState?.constructSpecificationMulti?.split('、')
return (
<>
<div className={`form-flex-wrap ${style}`} key={fixedKey}>
<div className="input-wrap mr5" style={{ width: '610px' }} key={`roof${index}`}>
<input type="text" className="input-light" value={roofList} readOnly />
</div>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" className="input-light" value={constructSpecificationMulti[index]} readOnly />
</div>
<div className={`form-flex-wrap ${style}`} key={`roof_${row}`}>
<div className="input-wrap mr5" style={{ width: '610px' }}>
<input type="text" className="input-light" value={roofList} readOnly />
</div>
</>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" className="input-light" value={constructSpecificationMulti[index]} readOnly />
</div>
</div>
)
})}
</td>
@ -1110,18 +1153,38 @@ export default function Estimate({ params }) {
<ul className="file-list">
{originFiles.map((originFile) => {
return (
<li className="file-item" key={uuidv4()}>
<span onClick={() => handleEstimateFileDownload(originFile)}>
{originFile.faileName}
<button
type="button"
className="delete"
onClick={(e) => {
deleteOriginFile(originFile.objectNo, originFile.no)
e.stopPropagation()
}}
></button>
</span>
<li className="file-item" key={originFile.no}>
{/* <li className="file-item" key={uuidv4()}> */}
<div className="file-item-wrap">
<span
style={{ display: originFile.delFlg === '0' ? '' : 'none' }}
onClick={() => handleEstimateFileDownload(originFile)}
>
{originFile.faileName}
<button
type="button"
className="delete"
onClick={(e) => {
deleteOriginFile(originFile.no)
e.stopPropagation()
}}
></button>
</span>
<div className="return-wrap" style={{ display: originFile.delFlg !== '0' ? '' : 'none' }}>
<span className="return">{originFile.faileName}</span>
<button
type="button"
className="return-btn"
onClick={(e) => {
returnOriginFile(originFile.no)
e.stopPropagation()
}}
>
<i className="return-ico"></i>
{getMessage('estimate.detail.fileList2.btn.return')}
</button>
</div>
</div>
</li>
)
})}
@ -1154,22 +1217,31 @@ export default function Estimate({ params }) {
{specialNoteList.length > 0 &&
specialNoteList.map((row) => {
return (
<div key={uuidv4()} className="special-note-check-item">
<div className="d-check-box light">
<input
type="checkbox"
id={row.code}
checked={!!row.check}
disabled={row.code === 'ATTR001' ? true : false}
onChange={(event) => {
setSpecialNoteList((specialNote) =>
specialNote.map((temp) => (temp.code === row.code ? { ...temp, check: !temp.check } : temp)),
)
settingShowContent(row.code, event)
// <div key={uuidv4()} className="special-note-check-item">
<div key={row.code} className="special-note-check-item">
<div className="special-note-check-box">
<div className="d-check-box light">
<input
type="checkbox"
id={row.code}
checked={!!row.check}
disabled={row.code === 'ATTR001' || row.pkgYn === '1' ? true : false}
onChange={() => {
setSpecialNoteList((specialNote) =>
specialNote.map((temp) => (temp.code === row.code ? { ...temp, check: !temp.check } : temp)),
)
}}
/>
<label htmlFor={row.code}></label>
</div>
<span
className="check-name"
onClick={() => {
settingShowContent(row.code)
}}
/>
<label htmlFor={row.code}>{row.codeNm}</label>
>
{row.codeNm}
</span>
</div>
</div>
)
@ -1185,7 +1257,7 @@ export default function Estimate({ params }) {
if (isObjectNotEmpty(showcontent)) {
return (
<dl key={uuidv4()}>
<dl key={row.code}>
<dt>{showcontent.codeNm}</dt>
<dd dangerouslySetInnerHTML={{ __html: showcontent.remarks }} style={{ whiteSpace: 'pre-wrap' }}></dd>
</dl>
@ -1200,12 +1272,23 @@ export default function Estimate({ params }) {
}
})
})
return pushData.map((item) => (
<dl key={uuidv4()}>
<dt>{item.codeNm}</dt>
<dd dangerouslySetInnerHTML={{ __html: item.remarks }} style={{ whiteSpace: 'pre-wrap' }}></dd>
</dl>
))
//
let filterData = pushData.filter((item) => uniqueData.includes(item.code))
if (filterData.length > 0) {
return filterData.map((item) => (
<dl key={item.code}>
<dt>{item.codeNm}</dt>
<dd dangerouslySetInnerHTML={{ __html: item.remarks }} style={{ whiteSpace: 'pre-wrap' }}></dd>
</dl>
))
} else {
return pushData.map((item) => (
<dl key={item.code}>
<dt>{item.codeNm}</dt>
<dd dangerouslySetInnerHTML={{ __html: item.remarks }} style={{ whiteSpace: 'pre-wrap' }}></dd>
</dl>
))
}
}
}
})}
@ -1231,19 +1314,19 @@ export default function Estimate({ params }) {
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totVolKw')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimalToFixed(estimateContextState?.totVolKw, 3)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimalToFixed(estimateContextState?.totVolKw, 2)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.supplyPrice')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(estimateContextState?.supplyPrice)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimalToFixed(estimateContextState?.supplyPrice, 0)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.vatPrice')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(estimateContextState?.vatPrice)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimalToFixed(estimateContextState?.vatPrice, 0)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totPrice')}</div>
<div className="estimate-name red">{convertNumberToPriceDecimal(estimateContextState?.totPrice)}</div>
<div className="estimate-name red">{convertNumberToPriceDecimalToFixed(estimateContextState?.totPrice, 0)}</div>
</div>
</div>
</div>
@ -1277,7 +1360,7 @@ export default function Estimate({ params }) {
</div>
</td>
<th>{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgWeight')}</th>
<td>{convertNumberToPriceDecimalToFixed(estimateContextState?.totVolKw, 3)}</td>
<td>{convertNumberToPriceDecimalToFixed(estimateContextState?.totVolKw, 2)}</td>
<th>{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgPrice')}</th>
<td>{convertNumberToPriceDecimal(estimateContextState?.pkgTotPrice)}</td>
</tr>
@ -1292,14 +1375,19 @@ export default function Estimate({ params }) {
<div className="select-wrap">
{session?.storeLvl === '1' ? (
<select
key={uuidv4()}
// key={uuidv4()}
className="select-light"
onChange={(e) => {
onChangeStorePriceList(e.target.value)
}}
value={showPriceCd}
>
{storePriceList.length > 0 && storePriceList.map((row) => <option value={row.priceCd}>{row.priceNm}</option>)}
{storePriceList.length > 0 &&
storePriceList.map((row) => (
<option key={row.priceCd} value={row.priceCd}>
{row.priceNm}
</option>
))}
</select>
) : (
<select key={uuidv4()} className="select-light">
@ -1409,7 +1497,7 @@ export default function Estimate({ params }) {
<div className="form-flex-wrap">
<div className="select-wrap mr5">
<Select
id="long-value-select1"
name="long-value-select1"
instanceId="long-value-select1"
className="react-select-custom"
classNamePrefix="custom"
@ -1477,15 +1565,17 @@ export default function Estimate({ params }) {
className="input-light al-r"
value={convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))}
disabled={
estimateContextState?.estimateType === 'YJSS'
? item?.paDispOrder
? true
: item.pkgMaterialFlg !== '1'
: item.itemId === '' || !!item?.paDispOrder
? true
: item.openFlg === '1'
item.openFlg === '1'
? true
: estimateContextState?.estimateType === 'YJSS'
? item?.paDispOrder
? true
: false
: item.pkgMaterialFlg !== '1'
: item.itemId === '' || !!item?.paDispOrder
? true
: item.openFlg === '1'
? true
: false
}
onChange={(e) => {
onChangeSalePrice(e.target.value, item.dispOrder, index)
@ -1501,7 +1591,15 @@ export default function Estimate({ params }) {
</div>
</td>
<td className="al-r">
{convertNumberToPriceDecimal(item?.showSaleTotPrice === '0' ? null : item?.saleTotPrice?.replaceAll(',', ''))}
{convertNumberToPriceDecimal(
item?.showSaleTotPrice === '0'
? null
: item?.amount === ''
? null
: item?.saleTotPrice === '0'
? null
: item?.saleTotPrice?.replaceAll(',', ''),
)}
</td>
</tr>
)

View File

@ -40,9 +40,8 @@ export default function ProductFeaturesPop({ popShowSpecialNoteList, showProduct
{showSpecialNoteList.length > 0 &&
showSpecialNoteList.map((row) => {
return (
<dl>
<dl key={row.code}>
<dt>{row.codeNm}</dt>
{/* <dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd> */}
<dd dangerouslySetInnerHTML={{ __html: row.remarks }} style={{ whiteSpace: 'pre-wrap' }}></dd>
</dl>
)

View File

@ -318,11 +318,11 @@ export default function CanvasMenu(props) {
<div className="ico-btn-from">
<button className="btn-frame gray ico-flx" onClick={() => setEstimatePopupOpen(true)}>
<span className="ico ico01"></span>
<span>{getMessage('plan.menu.estimate.docDown')}</span>
<span className="name">{getMessage('plan.menu.estimate.docDown')}</span>
</button>
<button className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
<span className="ico ico02"></span>
<span>{getMessage('plan.menu.estimate.save')}</span>
<span className="name">{getMessage('plan.menu.estimate.save')}</span>
</button>
<button
className="btn-frame gray ico-flx"
@ -331,7 +331,7 @@ export default function CanvasMenu(props) {
}}
>
<span className="ico ico03"></span>
<span>{getMessage('plan.menu.estimate.reset')}</span>
<span className="name">{getMessage('plan.menu.estimate.reset')}</span>
</button>
{estimateRecoilState?.docNo !== null && (sessionState.storeId === 'T01' || sessionState.storeLvl === '1') && (
@ -342,9 +342,13 @@ export default function CanvasMenu(props) {
}}
>
<span className="ico ico04"></span>
<span>{getMessage('plan.menu.estimate.copy')}</span>
<span className="name">{getMessage('plan.menu.estimate.copy')}</span>
</button>
)}
<button className="btn-frame gray ico-flx">
<span className="ico ico05"></span>
<span className="name">{getMessage('plan.menu.estimate.unLock')}</span>
</button>
</div>
</>
)}

View File

@ -20,7 +20,7 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
const orientationRef = useRef(null)
const { initEvent } = useEvent()
// const { initEvent } = useContext(EventContext)
const { makeModuleInstArea, manualModuleSetup, autoModuleSetup } = useModuleBasicSetting()
const { manualModuleSetup, autoModuleSetup, manualFlatroofModuleSetup, autoFlatroofModuleSetup } = useModuleBasicSetting()
const handleBtnNextStep = () => {
if (tabNum === 1) {
orientationRef.current.handleNextStep()
@ -28,20 +28,16 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
setTabNum(tabNum + 1)
}
useEffect(() => {
makeModuleInstArea() //
return () => {
initEvent() //
}
}, [])
const placementRef = {
isChidori: useRef('false'),
setupLocation: useRef('center'),
isMaxSetup: useRef('false'),
}
const placementFlatRef = {
setupLocation: useRef('south'),
}
return (
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap lx-2`}>
@ -66,7 +62,9 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
{/*배치면 초기설정 - 입력방법: 육지붕*/}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 2 && <PitchModule setTabNum={setTabNum} />}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 3 && <PitchPlacement setTabNum={setTabNum} />}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 3 && (
<PitchPlacement setTabNum={setTabNum} ref={placementFlatRef} />
)}
<div className="grid-btn-wrap">
{tabNum !== 1 && (
@ -80,14 +78,29 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
Next
</button>
)}
{tabNum === 3 && (
<>
<button className="btn-frame modal mr5" onClick={manualModuleSetup}>
{getMessage('modal.module.basic.setting.passivity.placement')}
</button>
<button className="btn-frame modal act" onClick={() => autoModuleSetup(placementRef)}>
{getMessage('modal.module.basic.setting.auto.placement')}
</button>
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && (
<>
<button className="btn-frame modal mr5" onClick={manualModuleSetup}>
{getMessage('modal.module.basic.setting.passivity.placement')}
</button>
<button className="btn-frame modal act" onClick={() => autoModuleSetup(placementRef)}>
{getMessage('modal.module.basic.setting.auto.placement')}
</button>
</>
)}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet === 3 && (
<>
<button className="btn-frame modal mr5" onClick={() => manualFlatroofModuleSetup(placementFlatRef)}>
{getMessage('modal.module.basic.setting.passivity.placement')}
</button>
<button className="btn-frame modal act" onClick={() => autoFlatroofModuleSetup(placementFlatRef)}>
{getMessage('modal.module.basic.setting.auto.placement')}
</button>
</>
)}
</>
)}
</div>

View File

@ -1,5 +1,6 @@
import { forwardRef, useEffect, useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { forwardRef, useState } from 'react'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
const Placement = forwardRef((props, refs) => {
const { getMessage } = useMessage()
@ -7,6 +8,12 @@ const Placement = forwardRef((props, refs) => {
const [setupLocation, setSetupLocation] = useState('center')
const [isMaxSetup, setIsMaxSetup] = useState('false')
const { makeModuleInstArea } = useModuleBasicSetting()
useEffect(() => {
makeModuleInstArea()
}, [])
const moduleData = {
header: [
{ type: 'check', name: '', prop: 'check', width: 70 },

View File

@ -1,7 +1,26 @@
import { forwardRef, useState, useEffect } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { compasDegAtom } from '@/store/orientationAtom'
import { canvasState } from '@/store/canvasAtom'
import { useRecoilValue } from 'recoil'
import { POLYGON_TYPE } from '@/common/common'
export default function PitchPlacement() {
const PitchPlacement = forwardRef((props, refs) => {
const { getMessage } = useMessage()
const [setupLocation, setSetupLocation] = useState('south')
const { makeModuleInstArea } = useModuleBasicSetting()
const compasDeg = useRecoilValue(compasDegAtom)
const canvas = useRecoilValue(canvasState)
useEffect(() => {
makeModuleInstArea()
}, [])
useEffect(() => {
handleChangeSetupLocation()
}, [setupLocation])
const moduleData = {
header: [
{ type: 'check', name: '', prop: 'check', width: 70 },
@ -24,6 +43,45 @@ export default function PitchPlacement() {
},
],
}
const handleSetupLocation = (e) => {
refs.setupLocation.current = e.target
setSetupLocation(e.target.value)
}
const handleChangeSetupLocation = () => {
if (setupLocation === 'south') {
canvas.getObjects().forEach((obj) => obj.name === 'flatExcretaLine' && canvas.remove(obj))
return null
} else {
const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //
moduleSetupSurfaces.forEach((surface, index) => {
console.log(`surface ${index} : `, surface)
const excretaLine = surface.lines
excretaLine.forEach((line) => {
line.set({
stroke: '#642EFB',
strokeWidth: 5,
surfaceId: surface.surfaceId,
name: 'flatExcretaLine',
})
canvas.add(line)
line.on('selected', () => {
excretaLine.forEach((obj) => obj.set({ stroke: '#642EFB', isSelected: false }))
if (!line.isSelected) {
line.set({ stroke: 'red', isSelected: true })
} else {
line.set({ stroke: '#642EFB', isSelected: false })
}
})
})
})
}
}
return (
<>
<div className="module-table-box mb10">
@ -88,11 +146,26 @@ export default function PitchPlacement() {
<div className="hexagonal-item">
<div className="pop-form-radio">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" />
<input
type="radio"
name="radio01"
id="ra01"
value={'south'}
checked={setupLocation === 'south'}
defaultChecked
onChange={handleSetupLocation}
/>
<label htmlFor="ra01">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.south')}</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra02" />
<input
type="radio"
name="radio01"
id="ra02"
value={'excreta'}
checked={setupLocation === 'excreta'}
onChange={handleSetupLocation}
/>
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.select')}</label>
</div>
</div>
@ -102,4 +175,6 @@ export default function PitchPlacement() {
</div>
</>
)
}
})
export default PitchPlacement

View File

@ -1,113 +1,26 @@
import { useEffect, useState } from 'react'
import { useRecoilState } from 'recoil'
import { canvasSettingState } from '@/store/canvasAtom'
import { basicSettingState } from '@/store/settingAtom'
import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup'
import 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 { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) {
const [objectNo, setObjectNo] = useState('test123241008001') //
const [showSizeGuideModal, setShowSizeGuidModal] = useState(false)
const [showMaterialGuideModal, setShowMaterialGuidModal] = useState(false)
const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(1)
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
const { closePopup } = usePopup()
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
const { getMessage } = useMessage()
const { get, post } = useAxios()
const { swalFire } = useSwal()
const { basicSetting, setBasicSettings, fetchBasicSettings, basicSettingSave } = useCanvasSetting()
//
useEffect(() => {
console.log('PlacementShapeSetting useEffect 실행')
fetchSettings()
}, [objectNo])
// PlacementShapeSetting
const fetchSettings = async () => {
try {
await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${objectNo}` }).then((res) => {
if (res.length == 0) return
// 'roofs'
const roofsRow = res.map((item) => {
return {
roofSizeSet: item.roofSizeSet,
roofAngleSet: item.roofAngleSet,
}
})
const roofsArray = res.some((item) => !item.roofSeq)
? // roofsArray
res.map(() => ({
roofApply: true,
roofSeq: 1,
roofType: 1,
roofWidth: 200,
roofHeight: 200,
roofHajebichi: 200,
roofGap: 0,
roofLayout: 'parallel',
}))
: res.map((item) => ({
roofApply: item.roofApply === '' || item.roofApply === false ? false : true,
roofSeq: item.roofSeq,
roofType: item.roofType,
roofWidth: item.roofWidth,
roofHeight: item.roofHeight,
roofHajebichi: item.roofHajebichi,
roofGap: item.roofGap,
roofLayout: item.roofLayout,
}))
console.log('roofsArray ', roofsArray)
// 'roofs' patternData
const patternData = {
roofSizeSet: roofsRow[0].roofSizeSet, //
roofAngleSet: roofsRow[0].roofAngleSet, //
roofs: roofsArray, // roofs
}
//
setBasicSettings({ ...patternData })
})
} catch (error) {
console.error('Data fetching error:', error)
}
if (!(Object.keys(canvasSetting).length === 0 && canvasSetting.constructor === Object)) {
setBasicSettings({ ...canvasSetting })
}
}
const submitCanvasConfig = async () => {
try {
const patternData = {
objectNo,
roofSizeSet: basicSetting.roofSizeSet,
roofAngleSet: basicSetting.roofAngleSet,
roofMaterialsAddList: basicSetting.roofs,
}
await post({ url: `/api/canvas-management/canvas-basic-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) })
})
//Recoil
setCanvasSetting({ ...basicSetting })
} catch (error) {
swalFire({ text: getMessage(res.returnMessage), icon: 'error' })
}
}
fetchBasicSettings()
}, [])
// Function to update the roofType and corresponding values
const handleRoofTypeChange = (index, value) => {
@ -122,7 +35,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
roofWidth: 265,
roofHeight: 235,
roofGap: 455,
hajebichi: 0,
roofHajebichi: 0,
}
} else if (roofType === 2) {
updatedRoofs[index] = {
@ -490,7 +403,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
</table>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act" onClick={() => submitCanvasConfig()}>
<button className="btn-frame modal act" onClick={() => basicSettingSave()}>
{getMessage('modal.common.save')}
</button>
</div>

View File

@ -14,7 +14,7 @@ export default function SizeGuide({ setShowSizeGuidModal }) {
<div className="placement-table light">
<table>
<colgroup>
<col style={{ width: '60px' }} />
<col style={{ width: '65px' }} />
<col />
</colgroup>
<tbody>

View File

@ -96,7 +96,8 @@ export default function Header(props) {
name: 'header.menus.management',
url: '',
children: [
{ id: 3, name: 'header.menus.management.newStuff', url: '/management/stuff/tempdetail', children: [] },
// { id: 3, name: 'header.menus.management.newStuff', url: '/management/stuff/tempdetail', children: [] },
{ id: 3, name: 'header.menus.management.newStuff', url: '/management/stuff/tempReg', children: [] },
{ id: 4, name: 'header.menus.management.stuffList', url: '/management/stuff', children: [] },
],
},
@ -154,7 +155,9 @@ export default function Header(props) {
onMouseEnter={(e) => ToggleonMouse(e, 'add', 'li > ul')}
onMouseLeave={(e) => ToggleonMouse(e, 'remove', 'li > ul')}
>
<Link href={m.url}>{getMessage(m.name)}</Link>
<Link scroll={false} href={m.url}>
{getMessage(m.name)}
</Link>
</li>
)
})}

View File

@ -128,9 +128,9 @@ export default function MainContents() {
className="recently-item"
onClick={() => {
if (row.tempFlg === '0') {
router.push(`/management/stuff/detail?objectNo=${row.objectNo.toString()}`)
router.push(`/management/stuff/detail?objectNo=${row.objectNo.toString()}`, { scroll: false })
} else {
router.push(`/management/stuff/tempdetail?objectNo=${row.objectNo.toString()}`)
router.push(`/management/stuff/tempdetail?objectNo=${row.objectNo.toString()}`, { scroll: false })
}
}}
>

View File

@ -75,7 +75,7 @@ export default function StuffDetail() {
installHeight: '', //
conType: '0', //( / )
remarks: '', //
tempFlag: 'T', //(1) (0)
tempFlg: 'T', //(1) (0)
}
const { register, setValue, getValues, handleSubmit, resetField, control, watch } = useForm({
defaultValues: formInitValue,
@ -108,7 +108,6 @@ export default function StuffDetail() {
const [editMode, setEditMode] = useState('NEW')
const { managementState, setManagementState } = useContext(ManagementContext)
const [planGridProps, setPlanGridProps] = useState({
planGridData: [],
isPageable: false,
@ -135,6 +134,7 @@ export default function StuffDetail() {
field: 'moduleModel',
headerName: getMessage('stuff.detail.planGridHeader.moduleModel'),
flex: 1,
wrapText: true,
cellStyle: { justifyContent: 'flex-start' /* 좌측정렬*/ },
},
{
@ -283,6 +283,7 @@ export default function StuffDetail() {
{getMessage('stuff.detail.planGrid.btn1')}
</button>
<button
style={buttonStyle}
type="button"
className="grid-btn"
onClick={() => {
@ -321,7 +322,7 @@ export default function StuffDetail() {
} else {
setManagementState({})
alert(getMessage('stuff.detail.header.notExistObjectNo'))
router.push('/management/stuff')
router.push('/management/stuff', { scroll: false })
}
if (isNotEmptyArray(res.data.planList)) {
setPlanGridProps({ ...planGridProps, planGridData: res.data.planList })
@ -333,7 +334,7 @@ export default function StuffDetail() {
setPlanGridProps({ ...planGridProps, planGridData: [] })
alert(getMessage('stuff.detail.header.notExistObjectNo'))
router.push('/management/stuff')
router.push('/management/stuff', { scroll: false })
}
})
} else {
@ -516,7 +517,6 @@ export default function StuffDetail() {
firstList = res
favList = res.filter((row) => row.priority !== 'B')
otherList = res.filter((row) => row.firstAgentYn === 'N')
setSaleStoreList(firstList)
setFavoriteStoreList(firstList)
setShowSaleStoreList(firstList)
@ -547,6 +547,9 @@ export default function StuffDetail() {
form.setValue('otherSaleStoreLevel', managementState.saleStoreLevel)
form.setValue('saleStoreLevel', '1')
form.setValue('saleStoreId', managementState.firstAgentId)
setSelOptions(managementState.firstAgentId)
}
//No.
@ -896,6 +899,8 @@ export default function StuffDetail() {
//
const setPlanReqInfo = (info) => {
// console.log('session :::::::', session)
// console.log(' :::::::', info)
form.setValue('planReqNo', info.planReqNo)
form.setValue('objectStatusId', info.building)
setSelectObjectStatusId(info.building)
@ -1376,7 +1381,7 @@ export default function StuffDetail() {
} else {
resetStuffRecoil()
}
router.push('/management/stuff')
router.push('/management/stuff', { scroll: false })
})
}
}
@ -2032,8 +2037,8 @@ export default function StuffDetail() {
onChange={onSelectionChange}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isClearable={managementState.tempFlg === '0' ? false : session?.storeLvl === '1' ? true : false}
isDisabled={managementState.tempFlg === '0' ? true : session?.storeLvl !== '1' ? true : false}
isClearable={managementState?.tempFlg === '0' ? false : session?.storeLvl === '1' ? true : false}
isDisabled={managementState?.tempFlg === '0' ? true : session?.storeLvl !== '1' ? true : false}
value={saleStoreList.filter(function (option) {
return option.saleStoreId === selOptions
})}
@ -2066,7 +2071,7 @@ export default function StuffDetail() {
getOptionValue={(x) => x.saleStoreId}
isClearable={false}
isDisabled={
managementState.tempFlg === '0'
managementState?.tempFlg === '0'
? true
: session?.storeLvl !== '1'
? true
@ -2151,9 +2156,13 @@ export default function StuffDetail() {
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isDisabled={
managementState.tempFlg === '0' ? true : session?.storeLvl === '1' && form.watch('saleStoreId') != '' ? false : true
managementState?.tempFlg === '0'
? true
: session?.storeLvl === '1' && form.watch('saleStoreId') != ''
? false
: true
}
isClearable={managementState.tempFlg === '0' ? false : session?.storeLvl === '1' ? true : false}
isClearable={managementState?.tempFlg === '0' ? false : session?.storeLvl === '1' ? true : false}
value={otherSaleStoreList.filter(function (option) {
return option.saleStoreId === otherSelOptions
})}

View File

@ -564,7 +564,8 @@ export default function StuffSearchCondition() {
<h3>{getMessage('stuff.search.title')}</h3>
</div>
<div className="left-unit-box">
<Link href="/management/stuff/tempdetail" scroll={false}>
<Link href="/management/stuff/tempReg" scroll={false}>
{/* <Link href="/management/stuff/tempdetail" scroll={false}> */}
<button type="button" className="btn-origin navy mr5">
{getMessage('stuff.search.btn.register')}
</button>

View File

@ -3,7 +3,7 @@ import { useContext, useEffect, useReducer, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { globalLocaleStore } from '@/store/localeAtom'
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { isObjectNotEmpty } from '@/util/common-utils'
import { isObjectNotEmpty, isEmptyArray, isNotEmptyArray } from '@/util/common-utils'
import { SessionContext } from '@/app/SessionProvider'
import { useMessage } from '@/hooks/useMessage'
import { useRouter } from 'next/navigation'
@ -18,6 +18,9 @@ const updateItemInList = (itemList, dispOrder, updates) => {
}
export const useEstimateController = (planNo) => {
const [fileList, setFileList] = useState([])
const [deleteFileList, setDeleteFileList] = useState([])
const router = useRouter()
const { session } = useContext(SessionContext)
const globalLocaleState = useRecoilValue(globalLocaleStore)
@ -39,6 +42,12 @@ export const useEstimateController = (planNo) => {
}
}, [])
useEffect(() => {
if (fileList.length > 0) {
realSave(fileList)
}
}, [fileList])
// 상세 조회
const fetchSetting = async (objectNo, planNo) => {
try {
@ -50,6 +59,7 @@ export const useEstimateController = (planNo) => {
item.delFlg = '0'
})
}
setEstimateContextState(res.data)
}
}
@ -146,19 +156,22 @@ export const useEstimateController = (planNo) => {
return alert(getMessage('estimate.detail.save.requiredEstimateDate'))
}
//첨부파일을 첨부안했는데
//아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼
console.log('새로추가첨부파일:::', estimateData.newFileList)
console.log('기존첨부파일:::', estimateData.originFiles)
// return
//기존에 첨부된 파일이 있으면 파일첨부관련 통과
if (estimateData?.originFiles?.length > 0) {
originFileFlg = true
let cnt = estimateData.originFiles.filter((file) => file.delFlg === '0').length
if (cnt == 0) {
originFileFlg = false
} else {
originFileFlg = true
}
}
if (flag) {
if (!originFileFlg) {
if (estimateData.newFileList?.length < 1) {
//기존에 첨부된 파일이 없으면
if (isEmptyArray(estimateData.newFileList)) {
//새로 첨부한 파일이 없으면
if (estimateData.itemList.length > 1) {
estimateData.itemList.map((row) => {
if (row.delFlg === '0') {
@ -181,9 +194,8 @@ export const useEstimateController = (planNo) => {
estimateData.itemList.map((item) => {
if (item.delFlg === '0') {
item.amount = item.amount?.replaceAll(',', '')
item.salePrice = parseFloat(item.salePrice?.replaceAll(',', '')).toFixed(2)
item.saleTotPrice = parseFloat(item.saleTotPrice?.replaceAll(',', '')).toFixed(2)
item.salePrice = Number(item.salePrice?.replaceAll(',', '')).toFixed(2)
item.saleTotPrice = Number(item.saleTotPrice?.replaceAll(',', '')).toFixed(2)
if (!item.paDispOrder) {
if (itemFlg) {
if (isNaN(item.amount)) {
@ -214,6 +226,17 @@ export const useEstimateController = (planNo) => {
}
}
})
estimateData.itemList = estimateData.itemList.filter((item) => item.delFlg === '0' || !item.addFlg)
let delCnt = 0
estimateData.itemList.map((item) => {
if (item.delFlg === '1') {
delCnt++
}
})
if (delCnt === estimateData.itemList.length) {
return alert(getMessage('estimate.detail.save.requiredItem'))
}
}
if (flag && fileFlg && itemFlg) {
@ -228,83 +251,93 @@ export const useEstimateController = (planNo) => {
formData.append('category', '10')
formData.append('userId', estimateData.userId)
await post({ url: '/api/file/fileUpload', data: formData })
}
//첨부파일저장끝
//제품라인 추가했는데 아이템 안고르고 저장하면itemId=''은 날리고 나머지 저장하기
// estimateData.itemList = estimateData.itemList.filter((item) => item.itemId !== '')
estimateData.itemList = estimateData.itemList.filter((item) => item.delFlg === '0' || !item.addFlg)
let delCnt = 0
estimateData.itemList.map((item) => {
if (item.delFlg === '1') {
delCnt++
}
})
if (delCnt === estimateData.itemList.length) {
return alert(getMessage('estimate.detail.save.requiredItem'))
}
let option = []
estimateData.itemList.forEach((item) => {
if (item.specialNoteCd) {
let split2 = item.specialNoteCd.split('、')
option = option.concat(split2)
}
})
let estimateOptions = ''
let estimateOptionsArray
estimateData.specialNoteList.map((item) => {
if (item.pkgYn === '0') {
if (item.check) {
if (estimateOptions === '') {
estimateOptions = item.code
} else {
estimateOptions += '、' + item.code
}
}
} else {
if (item.check) {
let flg = '0'
for (let i = 0; i < estimateData.uniqueData.length; i++) {
if (item.code.indexOf(estimateData.uniqueData[i]) > -1) {
flg = '1'
}
if (flg === '1') {
estimateOptions += '、' + estimateData.uniqueData[i]
}
}
}
}
})
estimateOptionsArray = estimateOptions.split('、').sort()
estimateOptionsArray = Array.from(new Set(estimateOptionsArray))
estimateOptions = estimateOptionsArray.join('、')
estimateData.estimateOption = estimateOptions
console.log('최종아이템:::', estimateData.itemList)
console.log('최종저장::', estimateData)
//2. 상세데이터 저장
// return
try {
await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => {
if (res.status === 201) {
alert(getMessage('estimate.detail.save.alertMsg'))
//어디로 보낼지
fetchSetting(objectRecoil.floorPlanObjectNo, estimateData.planNo)
}
await post({ url: '/api/file/fileUpload', data: formData }).then((res) => {
setFileList(res)
})
} catch (e) {
console.log('error::::::::::::', e.response.data.message)
} else {
setFileList([])
realSave()
}
}
}
const realSave = async (fileList) => {
//첨부파일저장끝
let option = []
estimateData.itemList.forEach((item) => {
if (item.specialNoteCd) {
let split2 = item.specialNoteCd.split('、')
option = option.concat(split2)
}
})
let estimateOptions = ''
let estimateOptionsArray
estimateData.specialNoteList.map((item) => {
if (item.pkgYn === '0') {
if (item.check) {
if (estimateOptions === '') {
estimateOptions = item.code
} else {
estimateOptions += '、' + item.code
}
}
} else {
if (item.check) {
let flg = '0'
for (let i = 0; i < estimateData.uniqueData.length; i++) {
if (item.code.indexOf(estimateData.uniqueData[i]) > -1) {
flg = '1'
}
if (flg === '1') {
estimateOptions += '、' + estimateData.uniqueData[i]
}
}
}
}
})
estimateOptionsArray = estimateOptions.split('、').sort()
estimateOptionsArray = Array.from(new Set(estimateOptionsArray))
estimateOptions = estimateOptionsArray.join('、')
estimateData.estimateOption = estimateOptions
// console.log('최종아이템:::', estimateData.itemList)
if (fileList?.length > 0) {
estimateData.fileList = fileList
} else {
estimateData.fileList = []
}
if (estimateData.originFiles?.length > 0) {
estimateData.deleteFileList = estimateData.originFiles?.filter((item) => item.delFlg === '1')
} else {
estimateData.deleteFileList = []
}
if (estimateData.estimateType === 'YJSS') {
estimateData.pkgAsp = estimateData.pkgAsp.replaceAll(',', '')
}
console.log('최종저장::', estimateData)
//2. 상세데이터 저장
// return
try {
await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => {
if (res.status === 201) {
estimateData.newFileList = []
// estimateData.originFiles = []
alert(getMessage('estimate.detail.save.alertMsg'))
//어디로 보낼지
fetchSetting(objectRecoil.floorPlanObjectNo, estimateData.planNo)
}
})
} catch (e) {
console.log('error::::::::::::', e.response.data.message)
}
}
/**
* 견적서 복사버튼
* (견적서 번호(estimateData.docNo) 생성된 이후 버튼 활성화 )

View File

@ -2,21 +2,27 @@ import { useRecoilState, useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom'
import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util'
import { roofDisplaySelector } from '@/store/settingAtom'
import offsetPolygon from '@/util/qpolygon-utils'
import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils'
import { QPolygon } from '@/components/fabric/QPolygon'
import { QLine } from '@/components/fabric/QLine'
import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom'
import { useEvent } from '@/hooks/useEvent'
import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common'
import * as turf from '@turf/turf'
import { v4 as uuidv4 } from 'uuid'
import { useSwal } from '@/hooks/useSwal'
import { canvasSettingState } from '@/store/canvasAtom'
import { compasDegAtom } from '@/store/orientationAtom'
export function useModuleBasicSetting() {
const canvas = useRecoilValue(canvasState)
const roofDisplay = useRecoilValue(roofDisplaySelector)
const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState)
// const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState)
const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState)
const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useEvent()
const { swalFire } = useSwal()
const canvasSetting = useRecoilValue(canvasSettingState)
const compasDeg = useRecoilValue(compasDegAtom)
// const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext)
let selectedModuleInstSurfaceArray = []
@ -62,6 +68,8 @@ export function useModuleBasicSetting() {
const surfaceId = uuidv4()
console.log('roof.moduleCompass', roof.moduleCompass)
let setupSurface = new QPolygon(offsetPoints, {
stroke: 'red',
fill: 'transparent',
@ -440,6 +448,7 @@ export function useModuleBasicSetting() {
//자동 모듈 설치(그리드 방식)
const autoModuleSetup = (placementRef) => {
initEvent() //마우스 이벤트 초기화
const isChidori = placementRef.isChidori.current === 'true' ? true : false
const setupLocation = placementRef.setupLocation.current
const isMaxSetup = placementRef.isMaxSetup.current === 'true' ? true : false
@ -472,14 +481,20 @@ export function useModuleBasicSetting() {
}
})
moduleSetupSurfaces.forEach((obj) => {
if (obj.modules) {
obj.modules.forEach((module) => {
canvas?.remove(module)
})
obj.modules = []
}
})
// console.log('moduleIsSetup', moduleIsSetup)
// if (moduleIsSetup.length > 0) {
// swalFire({ text: 'alert 아이콘 테스트입니다.', icon: 'error' })
// }
// moduleSetupSurfaces.forEach((obj) => {
// if (obj.modules) {
// obj.modules.forEach((module) => {
// canvas?.remove(module)
// })
// obj.modules = []
// }
// })
notSelectedTrestlePolygons.forEach((obj) => {
if (obj.modules) {
@ -614,20 +629,20 @@ export function useModuleBasicSetting() {
if (isMaxSetup) totalWidth = totalWidth * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함
for (let j = 0; j < diffTopEndPoint; j++) {
bottomMargin = j === 0 ? 1 : 0
bottomMargin = j === 0 ? 1 : 2
for (let i = 0; i <= totalWidth; i++) {
leftMargin = i === 0 ? 1.1 : 0 //숫자가 0이면 0, 1이면 1로 바꾸기
leftMargin = i === 0 ? 1 : 2
chidoriLength = 0
if (isChidori) {
chidoriLength = j % 2 === 0 ? 0 : width / 2
}
square = [
[startColPoint + tempMaxWidth * i - chidoriLength, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i + width - chidoriLength, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i + width - chidoriLength, startPoint.y1 - height * j - height - bottomMargin],
[startColPoint + tempMaxWidth * i - chidoriLength, startPoint.y1 - height * j - height - bottomMargin],
[startColPoint + tempMaxWidth * i - chidoriLength, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i - chidoriLength + leftMargin, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i + width - chidoriLength + leftMargin, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i + width - chidoriLength + leftMargin, startPoint.y1 - height * j - height - bottomMargin],
[startColPoint + tempMaxWidth * i - chidoriLength + leftMargin, startPoint.y1 - height * j - height - bottomMargin],
[startColPoint + tempMaxWidth * i - chidoriLength + leftMargin, startPoint.y1 - height * j - bottomMargin],
]
let squarePolygon = turf.polygon([square])
@ -674,9 +689,9 @@ export function useModuleBasicSetting() {
if (isMaxSetup) totalHeight = totalHeight * 2 //최대배치시 2배로 늘려서 반씩 검사
for (let i = 0; i <= totalWidth; i++) {
bottomMargin = i === 0 ? 1 : 0.1
bottomMargin = i === 0 ? 1 : 2
for (let j = 0; j < totalHeight; j++) {
leftMargin = i === 0 ? 0 : 0.5 * i
leftMargin = i === 0 ? 1 : 2
chidoriLength = 0
if (isChidori) {
chidoriLength = i % 2 === 0 ? 0 : height / 2
@ -746,17 +761,19 @@ export function useModuleBasicSetting() {
if (isMaxSetup) diffRightEndPoint = diffRightEndPoint * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함
for (let j = 0; j < diffBottomEndPoint; j++) {
bottomMargin = j === 0 ? 1 : 2
for (let i = 0; i < diffRightEndPoint; i++) {
leftMargin = i === 0 ? 1 : 2
chidoriLength = 0
if (isChidori) {
chidoriLength = j % 2 === 0 ? 0 : width / 2
}
square = [
[startColPoint + tempMaxWidth * i + chidoriLength, startPoint.y1 + height * j + 1],
[startColPoint + tempMaxWidth * i + chidoriLength, startPoint.y1 + height * j + height + 1],
[startColPoint + tempMaxWidth * i + width + chidoriLength, startPoint.y1 + height * j + height + 1],
[startColPoint + tempMaxWidth * i + width + chidoriLength, startPoint.y1 + height * j + 1],
[startColPoint + tempMaxWidth * i + chidoriLength, startPoint.y1 + height * j + 1],
[startColPoint + tempMaxWidth * i + chidoriLength + leftMargin, startPoint.y1 + height * j + bottomMargin],
[startColPoint + tempMaxWidth * i + chidoriLength + leftMargin, startPoint.y1 + height * j + height + bottomMargin],
[startColPoint + tempMaxWidth * i + width + chidoriLength + leftMargin, startPoint.y1 + height * j + height + bottomMargin],
[startColPoint + tempMaxWidth * i + width + chidoriLength + leftMargin, startPoint.y1 + height * j + bottomMargin],
[startColPoint + tempMaxWidth * i + chidoriLength + leftMargin, startPoint.y1 + height * j + bottomMargin],
]
let squarePolygon = turf.polygon([square])
@ -804,9 +821,9 @@ export function useModuleBasicSetting() {
if (isMaxSetup) totalHeight = totalHeight * 2 //최대배치시 2배로 늘려서 반씩 검사
for (let i = 0; i <= totalWidth; i++) {
bottomMargin = i === 0 ? 1 : 2
for (let j = 0; j < totalHeight; j++) {
bottomMargin = j === 0 ? 0.5 : 0.5 * j
leftMargin = i === 0 ? 0 : 0.5 * i
leftMargin = j === 0 ? 1 : 2
chidoriLength = 0
if (isChidori) {
chidoriLength = i % 2 === 0 ? 0 : height / 2
@ -912,11 +929,12 @@ export function useModuleBasicSetting() {
}
})
canvas?.renderAll()
//나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기
setupedModules.forEach((module, index) => {
if (isMaxSetup && index > 0) {
const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(setupedModules[index - 1]), polygonToTurfPolygon(module))
//겹치는지 확인
if (isOverlap) {
//겹쳐있으면 삭제
@ -930,142 +948,231 @@ export function useModuleBasicSetting() {
})
moduleSetupSurface.set({ modules: setupedModules })
// setModuleIsSetup(moduleSetupArray)
// const moduleArray = [...moduleIsSetup]
// moduleArray.push({
// surfaceId: moduleSetupSurface.surfaceId,
// moduleSetupArray: setupedModules,
// })
// setModuleIsSetup(moduleArray)
})
calculateForApi()
}
const calculateForApi = (moduleSetupArray) => {
// TODO : 현재는 남쪽기준. 동,서,북 분기처리 필요
const centerPoints = []
moduleSetupArray.forEach((module, index) => {
module.tempIndex = index
const { x, y } = module.getCenterPoint()
const { width, height } = module
centerPoints.push({ x, y, width, height, index })
const circle = new fabric.Circle({
radius: 5,
fill: 'red',
name: 'redCircle',
left: x - 5,
top: y - 5,
index: index,
selectable: false,
const calculateForApi = () => {
const moduleSufaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
const results = []
moduleSufaces.forEach((moduleSurface) => {
const centerPoints = []
const direction = moduleSurface.direction
const modules = moduleSurface.modules
modules.forEach((module, index) => {
module.tempIndex = index
const { x, y } = module.getCenterPoint()
const { width, height } = module
centerPoints.push({ x, y, width: Math.floor(width), height: Math.floor(height), index })
})
if (centerPoints.length === 0) return
//완전 노출 하면
let exposedBottom = 0
// 반 노출 하면
let exposedHalfBottom = 0
// 완전 노출 상면
let exposedTop = 0
//반 노출 상면
let exposedHalfTop = 0
// 완전 접면
let touchDimension = 0
//반접면
let halfTouchDimension = 0
// 노출하면 체크
centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint
// centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인
let bottomCell
let bottomLeftPoint
let bottomRightPoint
let leftBottomCnt
let rightBottomCnt
switch (direction) {
case 'south':
bottomCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - x) < 2 && Math.abs(centerPoint.y - (y + height)) < 2)
bottomLeftPoint = { x: x - width / 2, y: y + height }
bottomRightPoint = { x: x + width / 2, y: y + height }
break
case 'north':
bottomCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - x) < 2 && Math.abs(centerPoint.y - (y - height)) < 2)
bottomLeftPoint = { x: x + width / 2, y: y - height }
bottomRightPoint = { x: x - width / 2, y: y - height }
break
case 'east':
bottomCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - (x - width)) < 2 && Math.abs(centerPoint.y - y) < 2)
bottomLeftPoint = { x: x + width, y: y + height / 2 }
bottomRightPoint = { x: x + width, y: y - height / 2 }
break
case 'west':
bottomCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - (x + width)) < 2 && Math.abs(centerPoint.y - y) < 2)
bottomLeftPoint = { x: x - width, y: y - height / 2 }
bottomRightPoint = { x: x - width, y: y + height / 2 }
break
}
if (bottomCell.length === 1) {
return
}
// 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다.
leftBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - bottomLeftPoint.x) < 2 && Math.abs(centerPoint.y - bottomLeftPoint.y) < 2,
).length
rightBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - bottomRightPoint.x) < 2 && Math.abs(centerPoint.y - bottomRightPoint.y) < 2,
).length
if (leftBottomCnt + rightBottomCnt === 1) {
exposedHalfBottom++
return
}
})
// 노출상면 체크
centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint
let topCell
let topLeftPoint
let topRightPoint
let leftTopCnt
let rightTopCnt
switch (direction) {
case 'south':
topCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - x) < 2 && Math.abs(centerPoint.y - (y - height)) < 2)
topLeftPoint = { x: x - width / 2, y: y - height }
topRightPoint = { x: x + width / 2, y: y - height }
break
case 'north':
topCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - x) < 2 && Math.abs(centerPoint.y - (y + height)) < 2)
topLeftPoint = { x: x + width / 2, y: y + height }
topRightPoint = { x: x - width / 2, y: y + height }
break
case 'east':
topCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - (x - width)) < 2 && Math.abs(centerPoint.y - y) < 2)
topLeftPoint = { x: x - width, y: y + height / 2 }
topRightPoint = { x: x - width, y: y - height / 2 }
break
case 'west':
topCell = centerPoints.filter((centerPoint) => Math.abs(centerPoint.x - (x + width)) < 2 && Math.abs(centerPoint.y - y) < 2)
topLeftPoint = { x: x + width, y: y - height / 2 }
topRightPoint = { x: x + width, y: y + height / 2 }
break
}
if (topCell.length === 1) {
touchDimension++
return
}
leftTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - topLeftPoint.x) < 2 && Math.abs(centerPoint.y - topLeftPoint.y) < 2,
).length
rightTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - topRightPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2,
).length
if (leftTopCnt + rightTopCnt === 2) {
touchDimension++
return
}
if (leftTopCnt + rightTopCnt === 1) {
exposedHalfTop++
halfTouchDimension++
return
}
if (leftTopCnt + rightTopCnt === 0) {
exposedTop++
}
})
// 완전 노출 하면 계산
/*const cells = canvas.getObjects().filter((obj) => polygon.id === obj.parentId)
const points = cells.map((cell) => {
return cell.getCenterPoint()
})*/
const groupPoints = groupCoordinates(centerPoints, modules[0], direction)
groupPoints.forEach((group) => {
let maxY = group.reduce((acc, cur) => (acc.y > cur.y ? acc : cur)).y
let minY = group.reduce((acc, cur) => (acc.y < cur.y ? acc : cur)).y
let maxX = group.reduce((acc, cur) => (acc.x > cur.x ? acc : cur)).x
let minX = group.reduce((acc, cur) => (acc.x < cur.x ? acc : cur)).x
let maxYCenterPoint = group.filter((centerPoint) => Math.abs(centerPoint.y - maxY) < 2)
let minYCenterPoint = group.filter((centerPoint) => Math.abs(centerPoint.y - minY) < 2)
let maxXCenterPoint = group.filter((centerPoint) => Math.abs(centerPoint.x - maxX) < 2)
let minXCenterPoint = group.filter((centerPoint) => Math.abs(centerPoint.x - minX) < 2)
switch (direction) {
case 'south':
exposedBottom += maxYCenterPoint.length
break
case 'north':
exposedBottom += minYCenterPoint.length
break
case 'east':
exposedBottom += maxXCenterPoint.length
break
case 'west':
exposedBottom += minXCenterPoint.length
break
}
})
results.push({
exposedBottom,
exposedHalfBottom,
exposedTop,
exposedHalfTop,
touchDimension,
halfTouchDimension,
})
console.log({
direction,
exposedBottom,
exposedHalfBottom,
exposedTop,
exposedHalfTop,
touchDimension,
halfTouchDimension,
})
canvas.add(circle)
})
//완전 노출 하면
let exposedBottom = 0
// 반 노출 하면
let exposedHalfBottom = 0
// 완전 노출 상면
let exposedTop = 0
//반 노출 상면
let exposedHalfTop = 0
// 완전 접면
let touchDimension = 0
//반접면
let halfTouchDimension = 0
// 노출하면 체크
centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint
// centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인
const bottomCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y + height)) < 2)
if (bottomCell.length === 1) {
touchDimension++
return
}
const bottomLeftPoint = { x: x - width / 2, y: y + height }
const bottomRightPoint = { x: x + width / 2, y: y + height }
// 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다.
const leftBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - bottomLeftPoint.x) < 2 && Math.abs(centerPoint.y - bottomLeftPoint.y) < 2,
).length
const rightBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - bottomRightPoint.x) < 2 && Math.abs(centerPoint.y - bottomRightPoint.y) < 2,
).length
if (leftBottomCnt + rightBottomCnt === 2) {
touchDimension++
return
}
if (leftBottomCnt + rightBottomCnt === 1) {
halfTouchDimension++
exposedHalfBottom++
return
}
})
// 노출상면 체크
centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint
const topCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y - height)) < 2)
if (topCell.length === 1) {
return
}
const topLeftPoint = { x: x - width / 2, y: y - height }
const topRightPoint = { x: x + width / 2, y: y - height }
const leftTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - topLeftPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2,
).length
const rightTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - topRightPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2,
).length
if (leftTopCnt + rightTopCnt === 1) {
exposedHalfTop++
return
}
if (leftTopCnt + rightTopCnt === 0) {
exposedTop++
return
}
})
// 완전 노출 하면 계산
/*const cells = canvas.getObjects().filter((obj) => polygon.id === obj.parentId)
const points = cells.map((cell) => {
return cell.getCenterPoint()
})*/
const groupPoints = groupCoordinates(centerPoints)
groupPoints.forEach((group) => {
// 각 그룹에서 y값이 큰 값을 찾는다.
// 그리고 그 y값과 같은 값을 가지는 centerPoint를 찾는다.
const maxY = group.reduce((acc, cur) => (acc.y > cur.y ? acc : cur)).y
const maxYCenterPoint = group.filter((centerPoint) => Math.abs(centerPoint.y - maxY) < 2)
exposedBottom += maxYCenterPoint.length
})
return {
exposedBottom,
exposedHalfBottom,
exposedTop,
exposedHalfTop,
touchDimension,
halfTouchDimension,
}
return results
}
// polygon 내부 cell들의 centerPoint 배열을 그룹화 해서 반환
const groupCoordinates = (points) => {
const groupCoordinates = (points, moduleExample, direction) => {
const groups = []
const visited = new Set()
const width = 100
const height = 100
const horizonPadding = 5 // 가로 패딩
const verticalPadding = 3 // 세로 패딩
const width = Math.floor(moduleExample.width)
const height = Math.floor(moduleExample.height)
const horizonPadding = 0 // 가로 패딩
const verticalPadding = 0 // 세로 패딩
function isAdjacent(p1, p2) {
const dx = Math.abs(p1.x - p2.x)
const dy = Math.abs(p1.y - p2.y)
return (
(dx === width + horizonPadding && dy === 0) ||
(dx === 0 && dy === height + verticalPadding) ||
(dx === width / 2 + horizonPadding / 2 && dy === height + verticalPadding)
(Math.abs(width + horizonPadding - dx) < 2 && dy < 2) ||
(dx < 2 && Math.abs(dy - height + verticalPadding)) < 2 ||
(Math.abs(dx - width / 2 + horizonPadding / 2) < 2 && Math.abs(dy - height + verticalPadding) < 2)
)
}
@ -1182,7 +1289,7 @@ export function useModuleBasicSetting() {
const angle2 = Math.abs(Math.round(Math.atan(height2 / adjust2) * (180 / Math.PI) * 1000) / 1000)
const angle3 = 180 - (angle1 + angle2)
const charlie = 173.3 + 3 // 평행선길이 약간 여유를 줌
const charlie = 173.3 // 평행선길이 약간 여유를 줌
const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180)
const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180))
const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이
@ -1297,7 +1404,7 @@ export function useModuleBasicSetting() {
const angle2 = Math.abs(Math.round(Math.atan(adjust2 / height2) * (180 / Math.PI) * 1000) / 1000)
const angle3 = 180 - (angle1 + angle2)
const charlie = 173.3 + 3 // 평행선길이 약간 여유를줌
const charlie = 173.3 // 평행선길이 약간 여유를줌
const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180)
const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180))
@ -1413,10 +1520,343 @@ export function useModuleBasicSetting() {
return obj
}
const manualFlatroofModuleSetup = (placementFlatRef) => {
const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴
let applyAngle
let flatBatchType = placementFlatRef.setupLocation.current.value
let excretaLinesAngle = []
if (flatBatchType === 'south') {
applyAngle = compasDeg
} else {
const excretaLines = canvas.getObjects().filter((obj) => obj.name === 'flatExcretaLine' && obj.isSelected === true)
excretaLines.forEach((obj) => {
const points1 = { x: obj.x1, y: obj.y1 }
const points2 = { x: obj.x2, y: obj.y2 }
excretaLinesAngle.push({
surfaceId: obj.surfaceId,
angle: calculateAngle(points1, points2),
})
})
}
//calculateAngle
const batchObjects = canvas
?.getObjects()
.filter(
(obj) =>
obj.name === BATCH_TYPE.OPENING ||
obj.name === BATCH_TYPE.TRIANGLE_DORMER ||
obj.name === BATCH_TYPE.PENTAGON_DORMER ||
obj.name === BATCH_TYPE.SHADOW,
) //도머s 객체
const moduleOptions = {
fill: '#BFFD9F',
stroke: 'black',
strokeWidth: 0.1,
selectable: false, // 선택 가능하게 설정
lockMovementX: true, // X 축 이동 잠금
lockMovementY: true, // Y 축 이동 잠금
lockRotation: true, // 회전 잠금
lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금
opacity: 0.8,
parentId: moduleSetupSurface.parentId,
name: 'module',
}
function getRotatedCorners(rect) {
// 사각형의 중심점
const center = rect.getCenterPoint()
// 사각형의 원래 꼭짓점 좌표 (로컬 좌표 기준)
const halfWidth = (rect.width / 2) * rect.scaleX
const halfHeight = (rect.height / 2) * rect.scaleY
const corners = [
{ x: -halfWidth, y: -halfHeight }, // 좌상단
{ x: halfWidth, y: -halfHeight }, // 우상단
{ x: halfWidth, y: halfHeight }, // 우하단
{ x: -halfWidth, y: halfHeight }, // 좌하단
]
// 각 꼭짓점 좌표를 캔버스 좌표로 변환
const transformedCorners = corners.map((corner) => {
const point = new fabric.Point(corner.x, corner.y)
return fabric.util.transformPoint(point, rect.calcTransformMatrix())
})
return transformedCorners
}
function getSelectedExcretaLine() {}
if (moduleSetupSurfaces.length !== 0) {
let tempModule
let manualDrawModules = []
let inside = false
let turfPolygon
let flowDirection
let trestlePolygon
addCanvasMouseEventListener('mouse:move', (e) => {
//마우스 이벤트 삭제 후 재추가
const mousePoint = canvas.getPointer(e.e)
for (let i = 0; i < moduleSetupSurfaces.length; i++) {
turfPolygon = polygonToTurfPolygon(moduleSetupSurfaces[i])
trestlePolygon = moduleSetupSurfaces[i]
manualDrawModules = moduleSetupSurfaces[i].modules // 앞에서 자동으로 했을때 추가됨
flowDirection = moduleSetupSurfaces[i].flowDirection //도형의 방향
if (flatBatchType === 'excreta') {
const tempLine = excretaLinesAngle.find((obj) => obj.surfaceId === trestlePolygon.surfaceId)
if (tempLine) {
applyAngle = tempLine.angle
} else {
applyAngle = compasDeg
}
}
// const excretaLine = excretaLines.find((obj) => obj.isSelected === true && obj.surfaceId === trestlePolygon.surfaceId)
// console.log('excretaLine', excretaLine.x1, excretaLine.y1, excretaLine.x2, excretaLine.y2)
// applyAngle = calculateAngle(excretaLine.x1, excretaLine.y1, excretaLine.x2, excretaLine.y2)
// console.log('applyAngle', applyAngle)
let width = flowDirection === 'south' || flowDirection === 'north' ? 172 : 113
let height = flowDirection === 'south' || flowDirection === 'north' ? 113 : 172
const angledModule = new fabric.Rect({
width: width,
height: height,
left: mousePoint.x - width / 2,
top: mousePoint.y - height / 2,
})
const center = angledModule.getCenterPoint()
angledModule.set('angle', applyAngle)
angledModule.setPositionByOrigin(center, 'center', 'center')
const points = getRotatedCorners(angledModule) //
const turfPoints = coordToTurfPolygon(points)
if (turf.booleanWithin(turfPoints, turfPolygon)) {
let isDrawing = false
if (isDrawing) return
canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임
tempModule = new QPolygon(points, {
fill: 'white',
stroke: 'black',
strokeWidth: 0.3,
name: 'tempModule',
})
canvas?.add(tempModule) //움직여가면서 추가됨
canvas?.renderAll()
/**
* 스냅기능
*/
let snapDistance = 10
let cellSnapDistance = 20
const trestleLeft = moduleSetupSurfaces[i].left
const trestleTop = moduleSetupSurfaces[i].top
const trestleRight = trestleLeft + moduleSetupSurfaces[i].width * moduleSetupSurfaces[i].scaleX
const trestleBottom = trestleTop + moduleSetupSurfaces[i].height * moduleSetupSurfaces[i].scaleY
const bigCenterY = (trestleTop + trestleTop + moduleSetupSurfaces[i].height) / 2
// 작은 폴리곤의 경계 좌표 계산
const smallLeft = tempModule.left
const smallTop = tempModule.top
const smallRight = smallLeft + tempModule.width * tempModule.scaleX
const smallBottom = smallTop + tempModule.height * tempModule.scaleY
const smallCenterX = smallLeft + (tempModule.width * tempModule.scaleX) / 2
const smallCenterY = smallTop + (tempModule.height * tempModule.scaleX) / 2
if (manualDrawModules) {
manualDrawModules.forEach((cell) => {
const holdCellLeft = cell.left
const holdCellTop = cell.top
const holdCellRight = holdCellLeft + cell.width * cell.scaleX
const holdCellBottom = holdCellTop + cell.height * cell.scaleY
const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2
const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2
//설치된 셀에 좌측에 스냅
if (Math.abs(smallRight - holdCellLeft) < snapDistance) {
tempModule.left = holdCellLeft - width - 0.5
}
//설치된 셀에 우측에 스냅
if (Math.abs(smallLeft - holdCellRight) < snapDistance) {
tempModule.left = holdCellRight + 0.5
}
//설치된 셀에 위쪽에 스냅
if (Math.abs(smallBottom - holdCellTop) < snapDistance) {
tempModule.top = holdCellTop - height - 0.5
}
//설치된 셀에 밑쪽에 스냅
if (Math.abs(smallTop - holdCellBottom) < snapDistance) {
tempModule.top = holdCellBottom + 0.5
}
//가운데 -> 가운데
if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) {
tempModule.left = holdCellCenterX - width / 2
}
//왼쪽 -> 가운데
if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) {
tempModule.left = holdCellCenterX
}
// 오른쪽 -> 가운데
if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) {
tempModule.left = holdCellCenterX - width
}
//세로 가운데 -> 가운데
if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) {
tempModule.top = holdCellCenterY - height / 2
}
//위쪽 -> 가운데
if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) {
tempModule.top = holdCellCenterY
}
//아랫쪽 -> 가운데
if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) {
tempModule.top = holdCellCenterY - height
}
})
}
// 위쪽 변에 스냅
if (Math.abs(smallTop - trestleTop) < snapDistance) {
tempModule.top = trestleTop
}
// 아래쪽 변에 스냅
if (Math.abs(smallTop + tempModule.height * tempModule.scaleY - (trestleTop + moduleSetupSurfaces[i].height)) < snapDistance) {
tempModule.top = trestleTop + moduleSetupSurfaces[i].height - tempModule.height * tempModule.scaleY
}
// 왼쪽변에 스냅
if (Math.abs(smallLeft - trestleLeft) < snapDistance) {
tempModule.left = trestleLeft
}
//오른쪽 변에 스냅
if (Math.abs(smallRight - trestleRight) < snapDistance) {
tempModule.left = trestleRight - tempModule.width * tempModule.scaleX
}
if (flowDirection === 'south' || flowDirection === 'north') {
// 모듈왼쪽이 세로중앙선에 붙게 스냅
if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) {
tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2
}
// 모듈이 가운데가 세로중앙선에 붙게 스냅
if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) {
tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - (tempModule.width * tempModule.scaleX) / 2
}
// 모듈오른쪽이 세로중앙선에 붙게 스냅
if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) {
tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX
}
} else {
// 모듈이 가로중앙선에 스냅
if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < snapDistance) {
tempModule.top = bigCenterY - tempModule.height / 2
}
if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) {
tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2
}
// 모듈 밑면이 가로중앙선에 스냅
if (Math.abs(smallBottom - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) {
tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 - tempModule.height * tempModule.scaleY
}
}
inside = true
break
} else {
inside = false
}
}
if (!inside) {
// tempModule.set({ fill: 'red' })
canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule'))
canvas?.renderAll()
}
})
addCanvasMouseEventListener('mouse:up', (e) => {
let isIntersection = true
if (!inside) return
if (tempModule) {
const tempTurfModule = polygonToTurfPolygon(tempModule)
//도머 객체를 가져옴
if (batchObjects) {
batchObjects.forEach((object) => {
let dormerTurfPolygon
if (object.type === 'group') {
//도머는 그룹형태임
dormerTurfPolygon = batchObjectGroupToTurfPolygon(object)
} else {
//개구, 그림자
dormerTurfPolygon = polygonToTurfPolygon(rectToPolygon(object))
}
const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인
//겹치면 안됨
if (intersection) {
alert('도머위에 모듈을 올릴 수 없습니다.')
isIntersection = false
}
})
}
if (!isIntersection) return
if (turf.booleanWithin(tempTurfModule, turfPolygon)) {
//마우스 클릭시 set으로 해당 위치에 셀을 넣음
const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module))) //겹치는지 확인
if (!isOverlap) {
console.log('tempModule.points', tempModule.points)
let manualModule = new QPolygon(tempModule.points, { ...moduleOptions })
canvas?.add(manualModule)
manualDrawModules.push(tempModule)
} else {
alert('셀끼리 겹치면 안되죠?')
}
} else {
alert('나갔죠?!!')
}
}
})
}
}
const autoFlatroofModuleSetup = (placementFlatRef) => {}
return {
makeModuleInstArea,
manualModuleSetup,
autoModuleSetup,
restoreModuleInstArea,
manualFlatroofModuleSetup,
autoFlatroofModuleSetup,
}
}

View File

@ -1,6 +1,13 @@
import { useCallback, useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { adsorptionPointModeState, adsorptionRangeState, canvasState, planSizeSettingState, dotLineGridSettingState } from '@/store/canvasAtom'
import {
adsorptionPointModeState,
adsorptionRangeState,
canvasState,
planSizeSettingState,
dotLineGridSettingState,
canvasSettingState,
} from '@/store/canvasAtom'
import { globalLocaleStore } from '@/store/localeAtom'
import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
@ -11,6 +18,7 @@ import {
settingModalFirstOptionsState,
settingModalSecondOptionsState,
settingModalGridOptionsState,
basicSettingState,
} from '@/store/settingAtom'
import { POLYGON_TYPE } from '@/common/common'
import { globalFontAtom } from '@/store/fontAtom'
@ -70,6 +78,9 @@ export function useCanvasSetting() {
const [color, setColor] = useColor(gridColor ?? '#FF0000')
const [colorTemp, setColorTemp] = useState()
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
const SelectOptions = [
{ id: 1, name: getMessage('modal.canvas.setting.grid.dot.line.setting.line.origin'), value: 1 },
{ id: 2, name: '1/2', value: 1 / 2 },
@ -112,6 +123,14 @@ export function useCanvasSetting() {
console.log('useCanvasSetting 실행1', correntObjectNo)
}, [])
// 배치면 초기설정 변경 시
useEffect(() => {
//console.log('useCanvasSetting canvasSetting 실행', canvasSetting)
if (canvasSetting.flag) {
basicSettingSave()
}
}, [canvasSetting])
//흡착점 ON/OFF 변경 시
useEffect(() => {
//console.log('useCanvasSetting 실행2', adsorptionPointMode.fontFlag, correntObjectNo)
@ -232,6 +251,95 @@ export function useCanvasSetting() {
}
}
// 기본설정(PlacementShapeSetting) 조회 및 초기화
const fetchBasicSettings = async () => {
try {
await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}` }).then((res) => {
console.log('fetchBasicSettings res ', res)
if (res.length == 0) return
// 'roofs' 배열을 생성하여 각 항목을 추가
const roofsRow = res.map((item) => {
return {
roofSizeSet: item.roofSizeSet,
roofAngleSet: item.roofAngleSet,
}
})
const roofsArray = res.some((item) => !item.roofSeq)
? //최초 지붕재 추가 정보의 경우 roofsArray를 초기화 설정
res.map(() => ({
flag: false,
roofApply: true,
roofSeq: 1,
roofType: 1,
roofWidth: 265,
roofHeight: 235,
roofHajebichi: 0,
roofGap: 455,
// roofType: 1,
// roofWidth: 200,
// roofHeight: 200,
// roofHajebichi: 200,
// roofGap: 0,
roofLayout: 'parallel',
}))
: res.map((item) => ({
flag: false,
roofApply: item.roofApply === '' || item.roofApply === false ? false : true,
roofSeq: item.roofSeq,
roofType: item.roofType,
roofWidth: item.roofWidth,
roofHeight: item.roofHeight,
roofHajebichi: item.roofHajebichi,
roofGap: item.roofGap,
roofLayout: item.roofLayout,
}))
console.log('roofsArray ', roofsArray)
// 나머지 데이터와 함께 'roofs' 배열을 patternData에 넣음
const patternData = {
roofSizeSet: roofsRow[0].roofSizeSet, // 첫 번째 항목의 값을 사용
roofAngleSet: roofsRow[0].roofAngleSet, // 첫 번째 항목의 값을 사용
roofs: roofsArray, // 만들어진 roofs 배열
}
//console.error('patternData', patternData)
// 데이터 설정
setBasicSettings({ ...patternData })
})
} catch (error) {
console.error('Data fetching error:', error)
}
if (!(Object.keys(canvasSetting).length === 0 && canvasSetting.constructor === Object)) {
setBasicSettings({ ...canvasSetting })
}
//setCanvasSetting({ ...basicSetting })
}
// 기본설정(PlacementShapeSetting) 저장
const basicSettingSave = async () => {
try {
const patternData = {
objectNo: correntObjectNo,
roofSizeSet: basicSetting.roofSizeSet,
roofAngleSet: basicSetting.roofAngleSet,
roofMaterialsAddList: basicSetting.roofs,
}
await post({ url: `/api/canvas-management/canvas-basic-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) })
})
//Recoil 설정
setCanvasSetting({ ...basicSetting, flag: false })
} catch (error) {
swalFire({ text: getMessage(res.returnMessage), icon: 'error' })
}
}
// CanvasSetting 조회 및 초기화
const fetchSettings = async () => {
try {
const res = await get({ url: `/api/canvas-management/canvas-settings/by-object/${correntObjectNo}` })
@ -382,7 +490,7 @@ export function useCanvasSetting() {
}
}
// 옵션 클릭 후 저장
// CanvasSetting 옵션 클릭 후 저장
const onClickOption2 = useCallback(async () => {
// 서버에 전송할 데이터
const dataToSend = {
@ -592,7 +700,6 @@ export function useCanvasSetting() {
adsorptionRange,
setAdsorptionRange,
fetchSettings,
//onClickOption,
frontSettings,
globalFont,
setGlobalFont,
@ -621,5 +728,11 @@ export function useCanvasSetting() {
setGridColor,
color,
setColor,
canvasSetting,
setCanvasSetting,
basicSetting,
setBasicSettings,
fetchBasicSettings,
basicSettingSave,
}
}

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import { useRecoilState } from 'recoil'
import { v4 as uuidv4, validate as isValidUUID } from 'uuid'
import { v4 as uuidv4 } from 'uuid'
import { canvasState, currentCanvasPlanState, plansState, modifiedPlansState, modifiedPlanFlagState } from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage'
@ -179,7 +179,7 @@ export function usePlan() {
* objectNo에 해당하는 canvas 목록을 조회
*/
const getCanvasByObjectNo = async (userId, objectNo) => {
return get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}/${userId}` }).then((res) =>
return await get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}/${userId}` }).then((res) =>
res.map((item, index) => ({
id: item.id,
userId: item.userId,
@ -237,15 +237,15 @@ export function usePlan() {
/**
* id에 해당하는 canvas 데이터를 삭제
*/
const delCanvasById = (id) => {
return promiseDel({ url: `/api/canvas-management/canvas-statuses/by-id/${id}` })
const delCanvasById = async (id) => {
return await promiseDel({ url: `/api/canvas-management/canvas-statuses/by-id/${id}` })
}
/**
* objectNo에 해당하는 canvas 데이터들을 삭제
*/
const delCanvasByObjectNo = (objectNo) => {
return promiseDel({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` })
const delCanvasByObjectNo = async (objectNo) => {
return await promiseDel({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` })
}
/**
@ -282,19 +282,19 @@ export function usePlan() {
* 새로운 plan 생성
* 현재 plan의 데이터가 있을 경우 복제 여부를 확인
*/
const handleAddPlan = (userId, objectNo) => {
const handleAddPlan = async (userId, objectNo) => {
JSON.parse(currentCanvasData()).objects.length > 0
? swalFire({
text: `Plan ${currentCanvasPlan.ordering} ` + getMessage('plan.message.confirm.copy'),
type: 'confirm',
confirmFn: () => {
postCanvasStatus(userId, objectNo, currentCanvasData())
confirmFn: async () => {
await postCanvasStatus(userId, objectNo, currentCanvasData())
},
denyFn: () => {
postCanvasStatus(userId, objectNo, '')
denyFn: async () => {
await postCanvasStatus(userId, objectNo, '')
},
})
: postCanvasStatus(userId, objectNo, '')
: await postCanvasStatus(userId, objectNo, '')
}
/**
@ -317,10 +317,10 @@ export function usePlan() {
/**
* plan 삭제
*/
const handleDeletePlan = (e, id) => {
const handleDeletePlan = async (e, id) => {
e.stopPropagation() // 이벤트 버블링 방지
delCanvasById(id)
await delCanvasById(id)
.then((res) => {
setPlans((plans) => plans.filter((plan) => plan.id !== id))
setModifiedPlans((modifiedPlans) => modifiedPlans.filter((planId) => planId !== currentCanvasPlan.id))
@ -344,8 +344,8 @@ export function usePlan() {
/**
* plan 조회
*/
const loadCanvasPlanData = (userId, objectNo, pid) => {
getCanvasByObjectNo(userId, objectNo).then((res) => {
const loadCanvasPlanData = async (userId, objectNo, pid) => {
await getCanvasByObjectNo(userId, objectNo).then((res) => {
// console.log('canvas 목록 ', res)
if (res.length > 0) {
setPlans(res)

View File

@ -3,8 +3,8 @@ export const defaultSession = {}
export const sessionOptions = {
password: process.env.SESSION_SECRET,
cookieName: 'lama-session',
cookieOptions: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
},
// cookieOptions: {
// httpOnly: true,
// secure: process.env.NODE_ENV === 'production',
// },
}

View File

@ -164,6 +164,7 @@
"plan.menu.estimate.save": "保存",
"plan.menu.estimate.reset": "初期化",
"plan.menu.estimate.copy": "見積書のコピー",
"plan.menu.estimate.unLock": "ロック解除",
"plan.menu.simulation": "発展シミュレーション",
"plan.menu.simulation.excel": "Excel",
"plan.menu.simulation.pdf": "PDF",
@ -841,6 +842,7 @@
"estimate.detail.fileList.btn": "ファイル選択",
"estimate.detail.fileList.extCheck": "そのファイルはイメージファイルではありません",
"estimate.detail.header.fileList2": "添付ファイル一覧",
"estimate.detail.fileList2.btn.return": "復元",
"estimate.detail.header.specialEstimate": "見積もりの具体的な",
"estimate.detail.header.specialEstimateProductInfo": "製品情報",
"estimate.detail.sepcialEstimateProductInfo.totAmount": "数量 (PCS)",

View File

@ -168,6 +168,7 @@
"plan.menu.estimate.save": "저장",
"plan.menu.estimate.reset": "초기화",
"plan.menu.estimate.copy": "견적서 복사",
"plan.menu.estimate.unLock": "잠금 해제",
"plan.menu.simulation": "발전 시뮬레이션",
"plan.menu.simulation.excel": "Excel",
"plan.menu.simulation.pdf": "PDF",
@ -851,6 +852,7 @@
"estimate.detail.fileList.btn": "파일선택",
"estimate.detail.fileList.extCheck": "해당 파일은 이미지 파일이 아닙니다",
"estimate.detail.header.fileList2": "첨부파일 목록",
"estimate.detail.fileList2.btn.return": "복원",
"estimate.detail.header.specialEstimate": "견적특이사항",
"estimate.detail.header.specialEstimateProductInfo": "제품정보",
"estimate.detail.sepcialEstimateProductInfo.totAmount": "수량 (PCS)",

View File

@ -113,7 +113,7 @@ export const calculateFlowDirection = (canvasAngle) => {
return {
down: -canvasAngle,
up: 180 - canvasAngle,
left: 90 - canvasAngle,
right: -90 - canvasAngle,
left: 90 - canvasAngle < 180 ? 90 - canvasAngle : 90 - canvasAngle - 360,
right: -90 - canvasAngle < -180 ? -90 - canvasAngle + 360 : -90 - canvasAngle,
}
}