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 { correntObjectNoState } from '@/store/settingAtom'
import { notFound, usePathname, useSearchParams } from 'next/navigation' 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 { createContext, useReducer, useState } from 'react'
import { useSetRecoilState } from 'recoil' import { useSetRecoilState } from 'recoil'

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
const orientationRef = useRef(null) const orientationRef = useRef(null)
const { initEvent } = useEvent() const { initEvent } = useEvent()
// const { initEvent } = useContext(EventContext) // const { initEvent } = useContext(EventContext)
const { makeModuleInstArea, manualModuleSetup, autoModuleSetup } = useModuleBasicSetting() const { manualModuleSetup, autoModuleSetup, manualFlatroofModuleSetup, autoFlatroofModuleSetup } = useModuleBasicSetting()
const handleBtnNextStep = () => { const handleBtnNextStep = () => {
if (tabNum === 1) { if (tabNum === 1) {
orientationRef.current.handleNextStep() orientationRef.current.handleNextStep()
@ -28,20 +28,16 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
setTabNum(tabNum + 1) setTabNum(tabNum + 1)
} }
useEffect(() => {
makeModuleInstArea() //
return () => {
initEvent() //
}
}, [])
const placementRef = { const placementRef = {
isChidori: useRef('false'), isChidori: useRef('false'),
setupLocation: useRef('center'), setupLocation: useRef('center'),
isMaxSetup: useRef('false'), isMaxSetup: useRef('false'),
} }
const placementFlatRef = {
setupLocation: useRef('south'),
}
return ( return (
<WithDraggable isShow={true} pos={pos}> <WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap lx-2`}> <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 === 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"> <div className="grid-btn-wrap">
{tabNum !== 1 && ( {tabNum !== 1 && (
@ -80,7 +78,10 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
Next Next
</button> </button>
)} )}
{tabNum === 3 && ( {tabNum === 3 && (
<>
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && (
<> <>
<button className="btn-frame modal mr5" onClick={manualModuleSetup}> <button className="btn-frame modal mr5" onClick={manualModuleSetup}>
{getMessage('modal.module.basic.setting.passivity.placement')} {getMessage('modal.module.basic.setting.passivity.placement')}
@ -90,6 +91,18 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
</button> </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> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
import { forwardRef, useEffect, useState } from 'react'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { forwardRef, useState } from 'react' import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
const Placement = forwardRef((props, refs) => { const Placement = forwardRef((props, refs) => {
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -7,6 +8,12 @@ const Placement = forwardRef((props, refs) => {
const [setupLocation, setSetupLocation] = useState('center') const [setupLocation, setSetupLocation] = useState('center')
const [isMaxSetup, setIsMaxSetup] = useState('false') const [isMaxSetup, setIsMaxSetup] = useState('false')
const { makeModuleInstArea } = useModuleBasicSetting()
useEffect(() => {
makeModuleInstArea()
}, [])
const moduleData = { const moduleData = {
header: [ header: [
{ type: 'check', name: '', prop: 'check', width: 70 }, { 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 { 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 { 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 = { const moduleData = {
header: [ header: [
{ type: 'check', name: '', prop: 'check', width: 70 }, { 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 ( return (
<> <>
<div className="module-table-box mb10"> <div className="module-table-box mb10">
@ -88,11 +146,26 @@ export default function PitchPlacement() {
<div className="hexagonal-item"> <div className="hexagonal-item">
<div className="pop-form-radio"> <div className="pop-form-radio">
<div className="d-check-radio pop"> <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> <label htmlFor="ra01">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.south')}</label>
</div> </div>
<div className="d-check-radio pop"> <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> <label htmlFor="ra02">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.select')}</label>
</div> </div>
</div> </div>
@ -102,4 +175,6 @@ export default function PitchPlacement() {
</div> </div>
</> </>
) )
} })
export default PitchPlacement

View File

@ -1,113 +1,26 @@
import { useEffect, useState } from 'react' 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 { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide' import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide' import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
import WithDraggable from '@/components/common/draggable/WithDraggable' import WithDraggable from '@/components/common/draggable/WithDraggable'
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
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 [showSizeGuideModal, setShowSizeGuidModal] = useState(false) const [showSizeGuideModal, setShowSizeGuidModal] = useState(false)
const [showMaterialGuideModal, setShowMaterialGuidModal] = useState(false) const [showMaterialGuideModal, setShowMaterialGuidModal] = useState(false)
const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(1)
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
const { closePopup } = usePopup() const { closePopup } = usePopup()
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { get, post } = useAxios()
const { swalFire } = useSwal() const { basicSetting, setBasicSettings, fetchBasicSettings, basicSettingSave } = useCanvasSetting()
// //
useEffect(() => { useEffect(() => {
console.log('PlacementShapeSetting useEffect 실행') fetchBasicSettings()
}, [])
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' })
}
}
// Function to update the roofType and corresponding values // Function to update the roofType and corresponding values
const handleRoofTypeChange = (index, value) => { const handleRoofTypeChange = (index, value) => {
@ -122,7 +35,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
roofWidth: 265, roofWidth: 265,
roofHeight: 235, roofHeight: 235,
roofGap: 455, roofGap: 455,
hajebichi: 0, roofHajebichi: 0,
} }
} else if (roofType === 2) { } else if (roofType === 2) {
updatedRoofs[index] = { updatedRoofs[index] = {
@ -490,7 +403,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
</table> </table>
</div> </div>
<div className="grid-btn-wrap"> <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')} {getMessage('modal.common.save')}
</button> </button>
</div> </div>

View File

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

View File

@ -96,7 +96,8 @@ export default function Header(props) {
name: 'header.menus.management', name: 'header.menus.management',
url: '', url: '',
children: [ 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: [] }, { 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')} onMouseEnter={(e) => ToggleonMouse(e, 'add', 'li > ul')}
onMouseLeave={(e) => ToggleonMouse(e, 'remove', '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> </li>
) )
})} })}

View File

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

View File

@ -564,7 +564,8 @@ export default function StuffSearchCondition() {
<h3>{getMessage('stuff.search.title')}</h3> <h3>{getMessage('stuff.search.title')}</h3>
</div> </div>
<div className="left-unit-box"> <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"> <button type="button" className="btn-origin navy mr5">
{getMessage('stuff.search.btn.register')} {getMessage('stuff.search.btn.register')}
</button> </button>

View File

@ -3,7 +3,7 @@ import { useContext, useEffect, useReducer, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import { globalLocaleStore } from '@/store/localeAtom' 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, isEmptyArray, isNotEmptyArray } from '@/util/common-utils'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
@ -18,6 +18,9 @@ const updateItemInList = (itemList, dispOrder, updates) => {
} }
export const useEstimateController = (planNo) => { export const useEstimateController = (planNo) => {
const [fileList, setFileList] = useState([])
const [deleteFileList, setDeleteFileList] = useState([])
const router = useRouter() const router = useRouter()
const { session } = useContext(SessionContext) const { session } = useContext(SessionContext)
const globalLocaleState = useRecoilValue(globalLocaleStore) 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) => { const fetchSetting = async (objectNo, planNo) => {
try { try {
@ -50,6 +59,7 @@ export const useEstimateController = (planNo) => {
item.delFlg = '0' item.delFlg = '0'
}) })
} }
setEstimateContextState(res.data) setEstimateContextState(res.data)
} }
} }
@ -146,19 +156,22 @@ export const useEstimateController = (planNo) => {
return alert(getMessage('estimate.detail.save.requiredEstimateDate')) 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) { if (estimateData?.originFiles?.length > 0) {
let cnt = estimateData.originFiles.filter((file) => file.delFlg === '0').length
if (cnt == 0) {
originFileFlg = false
} else {
originFileFlg = true originFileFlg = true
} }
}
if (flag) { if (flag) {
if (!originFileFlg) { if (!originFileFlg) {
if (estimateData.newFileList?.length < 1) { //기존에 첨부된 파일이 없으면
if (isEmptyArray(estimateData.newFileList)) {
//새로 첨부한 파일이 없으면
if (estimateData.itemList.length > 1) { if (estimateData.itemList.length > 1) {
estimateData.itemList.map((row) => { estimateData.itemList.map((row) => {
if (row.delFlg === '0') { if (row.delFlg === '0') {
@ -181,9 +194,8 @@ export const useEstimateController = (planNo) => {
estimateData.itemList.map((item) => { estimateData.itemList.map((item) => {
if (item.delFlg === '0') { if (item.delFlg === '0') {
item.amount = item.amount?.replaceAll(',', '') item.amount = item.amount?.replaceAll(',', '')
item.salePrice = parseFloat(item.salePrice?.replaceAll(',', '')).toFixed(2) item.salePrice = Number(item.salePrice?.replaceAll(',', '')).toFixed(2)
item.saleTotPrice = parseFloat(item.saleTotPrice?.replaceAll(',', '')).toFixed(2) item.saleTotPrice = Number(item.saleTotPrice?.replaceAll(',', '')).toFixed(2)
if (!item.paDispOrder) { if (!item.paDispOrder) {
if (itemFlg) { if (itemFlg) {
if (isNaN(item.amount)) { 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) { if (flag && fileFlg && itemFlg) {
@ -228,24 +251,18 @@ export const useEstimateController = (planNo) => {
formData.append('category', '10') formData.append('category', '10')
formData.append('userId', estimateData.userId) formData.append('userId', estimateData.userId)
await post({ url: '/api/file/fileUpload', data: formData }) await post({ url: '/api/file/fileUpload', data: formData }).then((res) => {
} setFileList(res)
//첨부파일저장끝
//제품라인 추가했는데 아이템 안고르고 저장하면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) { } else {
return alert(getMessage('estimate.detail.save.requiredItem')) setFileList([])
realSave()
} }
}
}
const realSave = async (fileList) => {
//첨부파일저장끝
let option = [] let option = []
estimateData.itemList.forEach((item) => { estimateData.itemList.forEach((item) => {
@ -287,13 +304,30 @@ export const useEstimateController = (planNo) => {
estimateOptions = estimateOptionsArray.join('、') estimateOptions = estimateOptionsArray.join('、')
estimateData.estimateOption = estimateOptions estimateData.estimateOption = estimateOptions
console.log('최종아이템:::', estimateData.itemList) // 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) console.log('최종저장::', estimateData)
//2. 상세데이터 저장 //2. 상세데이터 저장
// return // return
try { try {
await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => { await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => {
if (res.status === 201) { if (res.status === 201) {
estimateData.newFileList = []
// estimateData.originFiles = []
alert(getMessage('estimate.detail.save.alertMsg')) alert(getMessage('estimate.detail.save.alertMsg'))
//어디로 보낼지 //어디로 보낼지
fetchSetting(objectRecoil.floorPlanObjectNo, estimateData.planNo) fetchSetting(objectRecoil.floorPlanObjectNo, estimateData.planNo)
@ -303,7 +337,6 @@ export const useEstimateController = (planNo) => {
console.log('error::::::::::::', e.response.data.message) console.log('error::::::::::::', e.response.data.message)
} }
} }
}
/** /**
* 견적서 복사버튼 * 견적서 복사버튼

View File

@ -2,21 +2,27 @@ import { useRecoilState, useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom' import { canvasState } from '@/store/canvasAtom'
import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util' import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util'
import { roofDisplaySelector } from '@/store/settingAtom' 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 { QPolygon } from '@/components/fabric/QPolygon'
import { QLine } from '@/components/fabric/QLine'
import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom' import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom'
import { useEvent } from '@/hooks/useEvent' import { useEvent } from '@/hooks/useEvent'
import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common' import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common'
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useSwal } from '@/hooks/useSwal'
import { canvasSettingState } from '@/store/canvasAtom'
import { compasDegAtom } from '@/store/orientationAtom'
export function useModuleBasicSetting() { export function useModuleBasicSetting() {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const roofDisplay = useRecoilValue(roofDisplaySelector) const roofDisplay = useRecoilValue(roofDisplaySelector)
const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState) const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState)
// const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState) const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState)
const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useEvent() const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useEvent()
const { swalFire } = useSwal()
const canvasSetting = useRecoilValue(canvasSettingState)
const compasDeg = useRecoilValue(compasDegAtom)
// const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext) // const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext)
let selectedModuleInstSurfaceArray = [] let selectedModuleInstSurfaceArray = []
@ -62,6 +68,8 @@ export function useModuleBasicSetting() {
const surfaceId = uuidv4() const surfaceId = uuidv4()
console.log('roof.moduleCompass', roof.moduleCompass)
let setupSurface = new QPolygon(offsetPoints, { let setupSurface = new QPolygon(offsetPoints, {
stroke: 'red', stroke: 'red',
fill: 'transparent', fill: 'transparent',
@ -440,6 +448,7 @@ export function useModuleBasicSetting() {
//자동 모듈 설치(그리드 방식) //자동 모듈 설치(그리드 방식)
const autoModuleSetup = (placementRef) => { const autoModuleSetup = (placementRef) => {
initEvent() //마우스 이벤트 초기화
const isChidori = placementRef.isChidori.current === 'true' ? true : false const isChidori = placementRef.isChidori.current === 'true' ? true : false
const setupLocation = placementRef.setupLocation.current const setupLocation = placementRef.setupLocation.current
const isMaxSetup = placementRef.isMaxSetup.current === 'true' ? true : false const isMaxSetup = placementRef.isMaxSetup.current === 'true' ? true : false
@ -472,14 +481,20 @@ export function useModuleBasicSetting() {
} }
}) })
moduleSetupSurfaces.forEach((obj) => { // console.log('moduleIsSetup', moduleIsSetup)
if (obj.modules) {
obj.modules.forEach((module) => { // if (moduleIsSetup.length > 0) {
canvas?.remove(module) // swalFire({ text: 'alert 아이콘 테스트입니다.', icon: 'error' })
}) // }
obj.modules = []
} // moduleSetupSurfaces.forEach((obj) => {
}) // if (obj.modules) {
// obj.modules.forEach((module) => {
// canvas?.remove(module)
// })
// obj.modules = []
// }
// })
notSelectedTrestlePolygons.forEach((obj) => { notSelectedTrestlePolygons.forEach((obj) => {
if (obj.modules) { if (obj.modules) {
@ -614,20 +629,20 @@ export function useModuleBasicSetting() {
if (isMaxSetup) totalWidth = totalWidth * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함 if (isMaxSetup) totalWidth = totalWidth * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함
for (let j = 0; j < diffTopEndPoint; j++) { for (let j = 0; j < diffTopEndPoint; j++) {
bottomMargin = j === 0 ? 1 : 0 bottomMargin = j === 0 ? 1 : 2
for (let i = 0; i <= totalWidth; i++) { for (let i = 0; i <= totalWidth; i++) {
leftMargin = i === 0 ? 1.1 : 0 //숫자가 0이면 0, 1이면 1로 바꾸기 leftMargin = i === 0 ? 1 : 2
chidoriLength = 0 chidoriLength = 0
if (isChidori) { if (isChidori) {
chidoriLength = j % 2 === 0 ? 0 : width / 2 chidoriLength = j % 2 === 0 ? 0 : width / 2
} }
square = [ square = [
[startColPoint + tempMaxWidth * i - chidoriLength, startPoint.y1 - height * j - bottomMargin], [startColPoint + tempMaxWidth * i - chidoriLength + leftMargin, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i + width - chidoriLength, startPoint.y1 - height * j - bottomMargin], [startColPoint + tempMaxWidth * i + width - chidoriLength + leftMargin, startPoint.y1 - height * j - bottomMargin],
[startColPoint + tempMaxWidth * i + width - chidoriLength, startPoint.y1 - height * j - height - bottomMargin], [startColPoint + tempMaxWidth * i + width - chidoriLength + leftMargin, startPoint.y1 - height * j - height - bottomMargin],
[startColPoint + tempMaxWidth * i - chidoriLength, startPoint.y1 - height * j - height - bottomMargin], [startColPoint + tempMaxWidth * i - chidoriLength + leftMargin, 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],
] ]
let squarePolygon = turf.polygon([square]) let squarePolygon = turf.polygon([square])
@ -674,9 +689,9 @@ export function useModuleBasicSetting() {
if (isMaxSetup) totalHeight = totalHeight * 2 //최대배치시 2배로 늘려서 반씩 검사 if (isMaxSetup) totalHeight = totalHeight * 2 //최대배치시 2배로 늘려서 반씩 검사
for (let i = 0; i <= totalWidth; i++) { 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++) { for (let j = 0; j < totalHeight; j++) {
leftMargin = i === 0 ? 0 : 0.5 * i leftMargin = i === 0 ? 1 : 2
chidoriLength = 0 chidoriLength = 0
if (isChidori) { if (isChidori) {
chidoriLength = i % 2 === 0 ? 0 : height / 2 chidoriLength = i % 2 === 0 ? 0 : height / 2
@ -746,17 +761,19 @@ export function useModuleBasicSetting() {
if (isMaxSetup) diffRightEndPoint = diffRightEndPoint * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함 if (isMaxSetup) diffRightEndPoint = diffRightEndPoint * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함
for (let j = 0; j < diffBottomEndPoint; j++) { for (let j = 0; j < diffBottomEndPoint; j++) {
bottomMargin = j === 0 ? 1 : 2
for (let i = 0; i < diffRightEndPoint; i++) { for (let i = 0; i < diffRightEndPoint; i++) {
leftMargin = i === 0 ? 1 : 2
chidoriLength = 0 chidoriLength = 0
if (isChidori) { if (isChidori) {
chidoriLength = j % 2 === 0 ? 0 : width / 2 chidoriLength = j % 2 === 0 ? 0 : width / 2
} }
square = [ square = [
[startColPoint + tempMaxWidth * i + chidoriLength, startPoint.y1 + height * j + 1], [startColPoint + tempMaxWidth * i + chidoriLength + leftMargin, startPoint.y1 + height * j + bottomMargin],
[startColPoint + tempMaxWidth * i + chidoriLength, startPoint.y1 + height * j + height + 1], [startColPoint + tempMaxWidth * i + chidoriLength + leftMargin, startPoint.y1 + height * j + height + bottomMargin],
[startColPoint + tempMaxWidth * i + width + chidoriLength, startPoint.y1 + height * j + height + 1], [startColPoint + tempMaxWidth * i + width + chidoriLength + leftMargin, startPoint.y1 + height * j + height + bottomMargin],
[startColPoint + tempMaxWidth * i + width + chidoriLength, startPoint.y1 + height * j + 1], [startColPoint + tempMaxWidth * i + width + chidoriLength + leftMargin, startPoint.y1 + height * j + bottomMargin],
[startColPoint + tempMaxWidth * i + chidoriLength, startPoint.y1 + height * j + 1], [startColPoint + tempMaxWidth * i + chidoriLength + leftMargin, startPoint.y1 + height * j + bottomMargin],
] ]
let squarePolygon = turf.polygon([square]) let squarePolygon = turf.polygon([square])
@ -804,9 +821,9 @@ export function useModuleBasicSetting() {
if (isMaxSetup) totalHeight = totalHeight * 2 //최대배치시 2배로 늘려서 반씩 검사 if (isMaxSetup) totalHeight = totalHeight * 2 //최대배치시 2배로 늘려서 반씩 검사
for (let i = 0; i <= totalWidth; i++) { for (let i = 0; i <= totalWidth; i++) {
bottomMargin = i === 0 ? 1 : 2
for (let j = 0; j < totalHeight; j++) { for (let j = 0; j < totalHeight; j++) {
bottomMargin = j === 0 ? 0.5 : 0.5 * j leftMargin = j === 0 ? 1 : 2
leftMargin = i === 0 ? 0 : 0.5 * i
chidoriLength = 0 chidoriLength = 0
if (isChidori) { if (isChidori) {
chidoriLength = i % 2 === 0 ? 0 : height / 2 chidoriLength = i % 2 === 0 ? 0 : height / 2
@ -912,11 +929,12 @@ export function useModuleBasicSetting() {
} }
}) })
canvas?.renderAll()
//나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기 //나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기
setupedModules.forEach((module, index) => { setupedModules.forEach((module, index) => {
if (isMaxSetup && index > 0) { if (isMaxSetup && index > 0) {
const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(setupedModules[index - 1]), polygonToTurfPolygon(module)) const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(setupedModules[index - 1]), polygonToTurfPolygon(module))
//겹치는지 확인 //겹치는지 확인
if (isOverlap) { if (isOverlap) {
//겹쳐있으면 삭제 //겹쳐있으면 삭제
@ -930,30 +948,36 @@ export function useModuleBasicSetting() {
}) })
moduleSetupSurface.set({ modules: setupedModules }) moduleSetupSurface.set({ modules: setupedModules })
// setModuleIsSetup(moduleSetupArray)
// const moduleArray = [...moduleIsSetup]
// moduleArray.push({
// surfaceId: moduleSetupSurface.surfaceId,
// moduleSetupArray: setupedModules,
// })
// setModuleIsSetup(moduleArray)
}) })
calculateForApi()
} }
const calculateForApi = (moduleSetupArray) => { const calculateForApi = () => {
// TODO : 현재는 남쪽기준. 동,서,북 분기처리 필요 const moduleSufaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
const results = []
moduleSufaces.forEach((moduleSurface) => {
const centerPoints = [] const centerPoints = []
moduleSetupArray.forEach((module, index) => { const direction = moduleSurface.direction
const modules = moduleSurface.modules
modules.forEach((module, index) => {
module.tempIndex = index module.tempIndex = index
const { x, y } = module.getCenterPoint() const { x, y } = module.getCenterPoint()
const { width, height } = module const { width, height } = module
centerPoints.push({ x, y, width, height, index }) centerPoints.push({ x, y, width: Math.floor(width), height: Math.floor(height), index })
const circle = new fabric.Circle({
radius: 5,
fill: 'red',
name: 'redCircle',
left: x - 5,
top: y - 5,
index: index,
selectable: false,
})
canvas.add(circle)
}) })
if (centerPoints.length === 0) return
//완전 노출 하면 //완전 노출 하면
let exposedBottom = 0 let exposedBottom = 0
// 반 노출 하면 // 반 노출 하면
@ -970,28 +994,48 @@ export function useModuleBasicSetting() {
centerPoints.forEach((centerPoint, index) => { centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint const { x, y, width, height } = centerPoint
// centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인 // centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인
const bottomCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y + height)) < 2) let bottomCell
if (bottomCell.length === 1) { let bottomLeftPoint
touchDimension++ let bottomRightPoint
return 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
} }
const bottomLeftPoint = { x: x - width / 2, y: y + height } if (bottomCell.length === 1) {
const bottomRightPoint = { x: x + width / 2, y: y + height } return
}
// 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다. // 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다.
const leftBottomCnt = centerPoints.filter( leftBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - bottomLeftPoint.x) < 2 && Math.abs(centerPoint.y - bottomLeftPoint.y) < 2, (centerPoint) => Math.abs(centerPoint.x - bottomLeftPoint.x) < 2 && Math.abs(centerPoint.y - bottomLeftPoint.y) < 2,
).length ).length
const rightBottomCnt = centerPoints.filter( rightBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - bottomRightPoint.x) < 2 && Math.abs(centerPoint.y - bottomRightPoint.y) < 2, (centerPoint) => Math.abs(centerPoint.x - bottomRightPoint.x) < 2 && Math.abs(centerPoint.y - bottomRightPoint.y) < 2,
).length ).length
if (leftBottomCnt + rightBottomCnt === 2) {
touchDimension++
return
}
if (leftBottomCnt + rightBottomCnt === 1) { if (leftBottomCnt + rightBottomCnt === 1) {
halfTouchDimension++
exposedHalfBottom++ exposedHalfBottom++
return return
} }
@ -1000,28 +1044,60 @@ export function useModuleBasicSetting() {
centerPoints.forEach((centerPoint, index) => { centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint const { x, y, width, height } = centerPoint
const topCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y - height)) < 2)
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) { if (topCell.length === 1) {
touchDimension++
return return
} }
const topLeftPoint = { x: x - width / 2, y: y - height } leftTopCnt = centerPoints.filter(
const topRightPoint = { x: x + width / 2, y: y - height } (centerPoint) => Math.abs(centerPoint.x - topLeftPoint.x) < 2 && Math.abs(centerPoint.y - topLeftPoint.y) < 2,
const leftTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - topLeftPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2,
).length ).length
const rightTopCnt = centerPoints.filter( rightTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - topRightPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2, (centerPoint) => Math.abs(centerPoint.x - topRightPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2,
).length ).length
if (leftTopCnt + rightTopCnt === 2) {
touchDimension++
return
}
if (leftTopCnt + rightTopCnt === 1) { if (leftTopCnt + rightTopCnt === 1) {
exposedHalfTop++ exposedHalfTop++
halfTouchDimension++
return return
} }
if (leftTopCnt + rightTopCnt === 0) { if (leftTopCnt + rightTopCnt === 0) {
exposedTop++ exposedTop++
return
} }
}) })
// 완전 노출 하면 계산 // 완전 노출 하면 계산
@ -1030,42 +1106,73 @@ export function useModuleBasicSetting() {
const points = cells.map((cell) => { const points = cells.map((cell) => {
return cell.getCenterPoint() return cell.getCenterPoint()
})*/ })*/
const groupPoints = groupCoordinates(centerPoints) const groupPoints = groupCoordinates(centerPoints, modules[0], direction)
groupPoints.forEach((group) => { groupPoints.forEach((group) => {
// 각 그룹에서 y값이 큰 값을 찾는다. let maxY = group.reduce((acc, cur) => (acc.y > cur.y ? acc : cur)).y
// 그리고 그 y값과 같은 값을 가지는 centerPoint를 찾는다. let minY = group.reduce((acc, cur) => (acc.y < cur.y ? acc : cur)).y
const maxY = group.reduce((acc, cur) => (acc.y > cur.y ? acc : cur)).y let maxX = group.reduce((acc, cur) => (acc.x > cur.x ? acc : cur)).x
const maxYCenterPoint = group.filter((centerPoint) => Math.abs(centerPoint.y - maxY) < 2) 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 exposedBottom += maxYCenterPoint.length
break
case 'north':
exposedBottom += minYCenterPoint.length
break
case 'east':
exposedBottom += maxXCenterPoint.length
break
case 'west':
exposedBottom += minXCenterPoint.length
break
}
}) })
return { results.push({
exposedBottom, exposedBottom,
exposedHalfBottom, exposedHalfBottom,
exposedTop, exposedTop,
exposedHalfTop, exposedHalfTop,
touchDimension, touchDimension,
halfTouchDimension, halfTouchDimension,
} })
console.log({
direction,
exposedBottom,
exposedHalfBottom,
exposedTop,
exposedHalfTop,
touchDimension,
halfTouchDimension,
})
})
return results
} }
// polygon 내부 cell들의 centerPoint 배열을 그룹화 해서 반환 // polygon 내부 cell들의 centerPoint 배열을 그룹화 해서 반환
const groupCoordinates = (points) => { const groupCoordinates = (points, moduleExample, direction) => {
const groups = [] const groups = []
const visited = new Set() const visited = new Set()
const width = 100 const width = Math.floor(moduleExample.width)
const height = 100 const height = Math.floor(moduleExample.height)
const horizonPadding = 5 // 가로 패딩 const horizonPadding = 0 // 가로 패딩
const verticalPadding = 3 // 세로 패딩 const verticalPadding = 0 // 세로 패딩
function isAdjacent(p1, p2) { function isAdjacent(p1, p2) {
const dx = Math.abs(p1.x - p2.x) const dx = Math.abs(p1.x - p2.x)
const dy = Math.abs(p1.y - p2.y) const dy = Math.abs(p1.y - p2.y)
return ( return (
(dx === width + horizonPadding && dy === 0) || (Math.abs(width + horizonPadding - dx) < 2 && dy < 2) ||
(dx === 0 && dy === height + verticalPadding) || (dx < 2 && Math.abs(dy - height + verticalPadding)) < 2 ||
(dx === width / 2 + horizonPadding / 2 && dy === height + verticalPadding) (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 angle2 = Math.abs(Math.round(Math.atan(height2 / adjust2) * (180 / Math.PI) * 1000) / 1000)
const angle3 = 180 - (angle1 + angle2) 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 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 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) // 높이 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 angle2 = Math.abs(Math.round(Math.atan(adjust2 / height2) * (180 / Math.PI) * 1000) / 1000)
const angle3 = 180 - (angle1 + angle2) 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 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 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 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 { return {
makeModuleInstArea, makeModuleInstArea,
manualModuleSetup, manualModuleSetup,
autoModuleSetup, autoModuleSetup,
restoreModuleInstArea, restoreModuleInstArea,
manualFlatroofModuleSetup,
autoFlatroofModuleSetup,
} }
} }

View File

@ -1,6 +1,13 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' 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 { globalLocaleStore } from '@/store/localeAtom'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
@ -11,6 +18,7 @@ import {
settingModalFirstOptionsState, settingModalFirstOptionsState,
settingModalSecondOptionsState, settingModalSecondOptionsState,
settingModalGridOptionsState, settingModalGridOptionsState,
basicSettingState,
} from '@/store/settingAtom' } from '@/store/settingAtom'
import { POLYGON_TYPE } from '@/common/common' import { POLYGON_TYPE } from '@/common/common'
import { globalFontAtom } from '@/store/fontAtom' import { globalFontAtom } from '@/store/fontAtom'
@ -70,6 +78,9 @@ export function useCanvasSetting() {
const [color, setColor] = useColor(gridColor ?? '#FF0000') const [color, setColor] = useColor(gridColor ?? '#FF0000')
const [colorTemp, setColorTemp] = useState() const [colorTemp, setColorTemp] = useState()
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
const SelectOptions = [ const SelectOptions = [
{ id: 1, name: getMessage('modal.canvas.setting.grid.dot.line.setting.line.origin'), value: 1 }, { id: 1, name: getMessage('modal.canvas.setting.grid.dot.line.setting.line.origin'), value: 1 },
{ id: 2, name: '1/2', value: 1 / 2 }, { id: 2, name: '1/2', value: 1 / 2 },
@ -112,6 +123,14 @@ export function useCanvasSetting() {
console.log('useCanvasSetting 실행1', correntObjectNo) console.log('useCanvasSetting 실행1', correntObjectNo)
}, []) }, [])
// 배치면 초기설정 변경 시
useEffect(() => {
//console.log('useCanvasSetting canvasSetting 실행', canvasSetting)
if (canvasSetting.flag) {
basicSettingSave()
}
}, [canvasSetting])
//흡착점 ON/OFF 변경 시 //흡착점 ON/OFF 변경 시
useEffect(() => { useEffect(() => {
//console.log('useCanvasSetting 실행2', adsorptionPointMode.fontFlag, correntObjectNo) //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 () => { const fetchSettings = async () => {
try { try {
const res = await get({ url: `/api/canvas-management/canvas-settings/by-object/${correntObjectNo}` }) 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 onClickOption2 = useCallback(async () => {
// 서버에 전송할 데이터 // 서버에 전송할 데이터
const dataToSend = { const dataToSend = {
@ -592,7 +700,6 @@ export function useCanvasSetting() {
adsorptionRange, adsorptionRange,
setAdsorptionRange, setAdsorptionRange,
fetchSettings, fetchSettings,
//onClickOption,
frontSettings, frontSettings,
globalFont, globalFont,
setGlobalFont, setGlobalFont,
@ -621,5 +728,11 @@ export function useCanvasSetting() {
setGridColor, setGridColor,
color, color,
setColor, setColor,
canvasSetting,
setCanvasSetting,
basicSetting,
setBasicSettings,
fetchBasicSettings,
basicSettingSave,
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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