Merge branch 'dev' into feature/test-jy
This commit is contained in:
commit
a2ef62ca15
@ -12,6 +12,7 @@
|
|||||||
"@nextui-org/react": "^2.4.2",
|
"@nextui-org/react": "^2.4.2",
|
||||||
"ag-grid-react": "^32.0.2",
|
"ag-grid-react": "^32.0.2",
|
||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
|
"chart.js": "^4.4.6",
|
||||||
"fabric": "^5.3.0",
|
"fabric": "^5.3.0",
|
||||||
"framer-motion": "^11.2.13",
|
"framer-motion": "^11.2.13",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
@ -22,6 +23,7 @@
|
|||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
"next-international": "^1.2.4",
|
"next-international": "^1.2.4",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-datepicker": "^7.3.0",
|
"react-datepicker": "^7.3.0",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
|||||||
@ -13,7 +13,6 @@ export async function GET(req) {
|
|||||||
const decodeUrl = decodeURIComponent(targetUrl)
|
const decodeUrl = decodeURIComponent(targetUrl)
|
||||||
|
|
||||||
const response = await fetch(decodeUrl)
|
const response = await fetch(decodeUrl)
|
||||||
|
|
||||||
const data = await response.arrayBuffer()
|
const data = await response.arrayBuffer()
|
||||||
const buffer = Buffer.from(data)
|
const buffer = Buffer.from(data)
|
||||||
|
|
||||||
|
|||||||
9
src/app/floor-plan/simulator/[mid]/page.jsx
Normal file
9
src/app/floor-plan/simulator/[mid]/page.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Simulator from '@/components/simulator/Simulator'
|
||||||
|
|
||||||
|
export default function SimulatorPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Simulator />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -9,12 +9,13 @@ import SingleDatePicker from '../common/datepicker/SingleDatePicker'
|
|||||||
import EstimateFileUploader from './EstimateFileUploader'
|
import EstimateFileUploader from './EstimateFileUploader'
|
||||||
import { useAxios } from '@/hooks/useAxios'
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
import { globalLocaleStore } from '@/store/localeAtom'
|
import { globalLocaleStore } from '@/store/localeAtom'
|
||||||
import { isNotEmptyArray, isObjectNotEmpty } from '@/util/common-utils'
|
import { isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||||
import Select from 'react-select'
|
|
||||||
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
||||||
import { SessionContext } from '@/app/SessionProvider'
|
import { SessionContext } from '@/app/SessionProvider'
|
||||||
|
import Select, { components } from 'react-select'
|
||||||
|
// import EstimateItemTable from './EstimateItemTable'
|
||||||
|
|
||||||
export default function Estimate({ params }) {
|
export default function Estimate({ params }) {
|
||||||
const { session } = useContext(SessionContext)
|
const { session } = useContext(SessionContext)
|
||||||
@ -33,6 +34,8 @@ export default function Estimate({ params }) {
|
|||||||
const { findCommonCode } = useCommonCode()
|
const { findCommonCode } = useCommonCode()
|
||||||
const [honorificCodeList, setHonorificCodeList] = useState([]) //경칭 공통코드
|
const [honorificCodeList, setHonorificCodeList] = useState([]) //경칭 공통코드
|
||||||
|
|
||||||
|
const [storePriceList, setStorePriceList] = useState([]) //가격표시 option
|
||||||
|
|
||||||
const [startDate, setStartDate] = useState(new Date())
|
const [startDate, setStartDate] = useState(new Date())
|
||||||
const singleDatePickerProps = {
|
const singleDatePickerProps = {
|
||||||
startDate,
|
startDate,
|
||||||
@ -44,7 +47,7 @@ export default function Estimate({ params }) {
|
|||||||
//견적서 상세데이터
|
//견적서 상세데이터
|
||||||
const { state, setState } = useEstimateController(params.pid)
|
const { state, setState } = useEstimateController(params.pid)
|
||||||
|
|
||||||
//견적특이사항 상세 데이터 LIST
|
const [itemList, setItemList] = useState([])
|
||||||
|
|
||||||
//견적특이사항 List
|
//견적특이사항 List
|
||||||
const [specialNoteList, setSpecialNoteList] = useState([])
|
const [specialNoteList, setSpecialNoteList] = useState([])
|
||||||
@ -155,6 +158,46 @@ export default function Estimate({ params }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//아이템 목록
|
||||||
|
useEffect(() => {
|
||||||
|
if (isNotEmptyArray(state.itemList)) {
|
||||||
|
setItemList(state.itemList)
|
||||||
|
}
|
||||||
|
}, [state?.itemList])
|
||||||
|
|
||||||
|
//가격표시 option 세팅
|
||||||
|
useEffect(() => {
|
||||||
|
const param = {
|
||||||
|
saleStoreId: session.storeId,
|
||||||
|
sapSalesStoreCd: session.custCd,
|
||||||
|
docTpCd: state?.estimateType,
|
||||||
|
}
|
||||||
|
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
|
||||||
|
get({ url: apiUrl }).then((res) => {
|
||||||
|
if (isNotEmptyArray(res?.data)) {
|
||||||
|
setStorePriceList(res.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [state?.estimateType])
|
||||||
|
|
||||||
|
//Pricing 버튼
|
||||||
|
const handlePricing = async (priceCd) => {
|
||||||
|
const param = {
|
||||||
|
saleStoreId: session.storeId,
|
||||||
|
sapSalesStoreCd: session.custCd,
|
||||||
|
docTpCd: state.estimateType,
|
||||||
|
priceCd: priceCd,
|
||||||
|
itemIdList: [], //아이템
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('param::', param)
|
||||||
|
return
|
||||||
|
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
|
||||||
|
console.log('프라이싱결과::::::', res)
|
||||||
|
//아이템쪽 다 새로고침............SUCK!!!
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sub-content estimate">
|
<div className="sub-content estimate">
|
||||||
<div className="sub-content-inner">
|
<div className="sub-content-inner">
|
||||||
@ -304,6 +347,7 @@ export default function Estimate({ params }) {
|
|||||||
value={'YJSS'}
|
value={'YJSS'}
|
||||||
checked={state?.estimateType === 'YJSS' ? true : false}
|
checked={state?.estimateType === 'YJSS' ? true : false}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
//주문분류
|
||||||
setState({ estimateType: e.target.value })
|
setState({ estimateType: e.target.value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -380,9 +424,18 @@ export default function Estimate({ params }) {
|
|||||||
<div className="title-wrap">
|
<div className="title-wrap">
|
||||||
<h3>{getMessage('estimate.detail.header.fileList1')}</h3>
|
<h3>{getMessage('estimate.detail.header.fileList1')}</h3>
|
||||||
<div className="d-check-box light mr5">
|
<div className="d-check-box light mr5">
|
||||||
<input type="checkbox" id="next" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="next"
|
||||||
|
checked={state?.fileFlg === '0' ? false : true}
|
||||||
|
onChange={(e) => {
|
||||||
|
setState({
|
||||||
|
fileFlg: e.target.checked ? '1' : '0',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<label htmlFor="next" style={{ color: '#101010' }}>
|
<label htmlFor="next" style={{ color: '#101010' }}>
|
||||||
{getMessage('estimate.detail.nextSubmit')}
|
{getMessage('estimate.detail.fileFlg')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -416,7 +469,7 @@ export default function Estimate({ params }) {
|
|||||||
<td>
|
<td>
|
||||||
<div className="drag-file-box">
|
<div className="drag-file-box">
|
||||||
<ul className="file-list">
|
<ul className="file-list">
|
||||||
{isNotEmptyArray(originFiles) &&
|
{originFiles.length > 0 &&
|
||||||
originFiles.map((originFile) => {
|
originFiles.map((originFile) => {
|
||||||
return (
|
return (
|
||||||
<li className="file-item">
|
<li className="file-item">
|
||||||
@ -486,13 +539,13 @@ export default function Estimate({ params }) {
|
|||||||
return (
|
return (
|
||||||
<dl>
|
<dl>
|
||||||
<dt>{row.codeNm}</dt>
|
<dt>{row.codeNm}</dt>
|
||||||
<dd>{row.remarks}</dd>
|
<dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd>
|
||||||
</dl>
|
</dl>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{/* 견적특이사항 선택한 내용?영역끝 */}
|
{/* 견적특이사항 선택한 내용 영역끝 */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -565,12 +618,30 @@ export default function Estimate({ params }) {
|
|||||||
<div className="product-price-wrap">
|
<div className="product-price-wrap">
|
||||||
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
|
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
|
||||||
<div className="select-wrap">
|
<div className="select-wrap">
|
||||||
<select className="select-light" name="" id="">
|
{session?.storeLvl === '1' ? (
|
||||||
<option value="">111</option>
|
<select
|
||||||
<option value="">222</option>
|
className="select-light"
|
||||||
</select>
|
onChange={(e) => {
|
||||||
|
setState({ priceCd: e.target.value })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{storePriceList.length > 0 && storePriceList.map((row) => <option value={row.priceCd}>{row.priceNm}</option>)}
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<select className="select-light">
|
||||||
|
<option value="UNIT_PRICE">{getMessage('estimate.detail.header.unitPrice')}</option>
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button className="btn-origin grey ml5">{getMessage('estimate.detail.showPrice.btn1')}</button>
|
<button
|
||||||
|
className="btn-origin grey ml5"
|
||||||
|
onClick={() => {
|
||||||
|
// console.log('priceCd::', state.priceCd)
|
||||||
|
handlePricing(state.priceCd)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getMessage('estimate.detail.showPrice.pricingBtn')}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="product-edit-wrap">
|
<div className="product-edit-wrap">
|
||||||
<ul className="product-edit-explane">
|
<ul className="product-edit-explane">
|
||||||
@ -605,7 +676,83 @@ export default function Estimate({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
{/* 가격표시영역끝 */}
|
{/* 가격표시영역끝 */}
|
||||||
{/* html테이블시작 */}
|
{/* html테이블시작 */}
|
||||||
<div className="q-grid no-cols"></div>
|
<div className="esimate-table">
|
||||||
|
<table>
|
||||||
|
<colgroup>
|
||||||
|
<col width={50} />
|
||||||
|
<col width={100} />
|
||||||
|
<col />
|
||||||
|
<col width={200} />
|
||||||
|
<col width={100} />
|
||||||
|
<col width={100} />
|
||||||
|
<col width={200} />
|
||||||
|
<col width={240} />
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<div className="d-check-box pop no-text" style={{ display: 'none' }}>
|
||||||
|
<input type="checkbox" id="ch97" />
|
||||||
|
<label htmlFor="ch97"></label>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col1')}</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col2')}</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col3')}</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col4')}</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col5')}</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col6')}</th>
|
||||||
|
<th>{getMessage('estimate.detail.itemTableHeader.col7')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="al-c">
|
||||||
|
<div className="d-check-box light no-text">
|
||||||
|
<input type="checkbox" id="ch98" />
|
||||||
|
<label htmlFor="ch98"></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="al-r">100</td>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="select-wrap mr5">{/* <Select /> */}</div>
|
||||||
|
<div className="btn-area">
|
||||||
|
<span className="tb_ico change_check"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="name">HNW-MC4-CHN30</div>
|
||||||
|
<div className="icon-wrap">
|
||||||
|
<span className="tb_ico file_check"></span>
|
||||||
|
<button className="grid-tip"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="input-wrap" style={{ width: '100%' }}>
|
||||||
|
<input type="text" className="input-light al-r" defaultValue={'20'} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>セット</td>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="input-wrap mr5">
|
||||||
|
<input type="text" className="input-light al-r" defaultValue={'278,050'} />
|
||||||
|
</div>
|
||||||
|
<div className="btn-area">
|
||||||
|
<span className="tb_ico open_check"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="al-r">5,561,000</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* html테이블끝 */}
|
{/* html테이블끝 */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export default function EstimateFileUploader({ uploadFiles, setUploadFiles }) {
|
|||||||
// if (res.data > 0) setUploadFiles([...files, { name: e.target.files[0].name, id: uuidv4() }])
|
// if (res.data > 0) setUploadFiles([...files, { name: e.target.files[0].name, id: uuidv4() }])
|
||||||
// })
|
// })
|
||||||
setUploadFiles([...uploadFiles, { data: e.target.files[0], id: uuidv4() }])
|
setUploadFiles([...uploadFiles, { data: e.target.files[0], id: uuidv4() }])
|
||||||
|
e.target.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteFile = (id) => {
|
const deleteFile = (id) => {
|
||||||
|
|||||||
255
src/components/estimate/popup/DocDownOptionPop.jsx
Normal file
255
src/components/estimate/popup/DocDownOptionPop.jsx
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
'use client'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
|
import { useRecoilValue } from 'recoil'
|
||||||
|
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||||
|
|
||||||
|
export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) {
|
||||||
|
// console.log('플랜번호::::::::::::', planNo)
|
||||||
|
const { getMessage } = useMessage()
|
||||||
|
const { promiseGet } = useAxios()
|
||||||
|
|
||||||
|
//다운로드 파일 EXCEL
|
||||||
|
const [schUnitPriceFlg, setSchUnitPriceFlg] = useState('0')
|
||||||
|
|
||||||
|
//견적제출서 표시명
|
||||||
|
const [schDisplayFlg, setSchSchDisplayFlg] = useState('0')
|
||||||
|
//가대 중량표 포함
|
||||||
|
const [schWeightFlg, setSchWeightFlg] = useState('0')
|
||||||
|
//도면/시뮬레이션 파일 포함
|
||||||
|
const [schDrawingFlg, setSchDrawingFlg] = useState('0')
|
||||||
|
|
||||||
|
// recoil 물건번호
|
||||||
|
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||||
|
|
||||||
|
//문서 다운로드
|
||||||
|
const handleFileDown = async () => {
|
||||||
|
// console.log('물건번호:::', objectRecoil.floorPlanObjectNo)
|
||||||
|
// console.log('planNo::', planNo)
|
||||||
|
// 고른 옵션값들
|
||||||
|
//0 : 견적가 Excel 1 : 정가용Excel 2: 견적가 PDF 3 :정가용PDF
|
||||||
|
// console.log(schUnitPriceFlg)
|
||||||
|
// console.log(schDisplayFlg)
|
||||||
|
// console.log(schWeightFlg)
|
||||||
|
// console.log(schDrawingFlg)
|
||||||
|
const url = '/api/estimate/excel-download'
|
||||||
|
const params = {}
|
||||||
|
const options = { responseType: 'blob' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-popup">
|
||||||
|
<div className="modal-dialog middle">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="title">{getMessage('estimate.detail.docPopup.title')}</h1>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="modal-close"
|
||||||
|
onClick={() => {
|
||||||
|
setEstimatePopupOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getMessage('estimate.detail.docPopup.close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="modal-body-inner">
|
||||||
|
<div className="explane">{getMessage('estimate.detail.docPopup.explane')}</div>
|
||||||
|
<div className="common-table">
|
||||||
|
<table>
|
||||||
|
<colgroup>
|
||||||
|
<col style={{ width: '260px' }} />
|
||||||
|
<col />
|
||||||
|
</colgroup>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{getMessage('estimate.detail.docPopup.schUnitPriceFlg')}
|
||||||
|
<span className="red">*</span>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="d-check-radio light mr10">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="schUnitPriceFlg0"
|
||||||
|
name="schUnitPriceFlg"
|
||||||
|
value={'0'}
|
||||||
|
checked={schUnitPriceFlg === '0'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchUnitPriceFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schUnitPriceFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio light mr10">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="schUnitPriceFlg1"
|
||||||
|
name="schUnitPriceFlg"
|
||||||
|
value={'1'}
|
||||||
|
checked={schUnitPriceFlg === '1'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchUnitPriceFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schUnitPriceFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio light mr10">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="schUnitPricePdfFlg0"
|
||||||
|
name="schUnitPriceFlg"
|
||||||
|
value={'2'}
|
||||||
|
checked={schUnitPriceFlg === '2'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchUnitPriceFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schUnitPricePdfFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio light ">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="schUnitPricePdfFlg1"
|
||||||
|
name="schUnitPriceFlg"
|
||||||
|
value={'3'}
|
||||||
|
checked={schUnitPriceFlg === '3'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchUnitPriceFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schUnitPricePdfFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{getMessage('estimate.detail.docPopup.schDisplayFlg')} <span className="red">*</span>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="d-check-radio light mr10">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="schDisplayFlg"
|
||||||
|
id="schDisplayFlg0"
|
||||||
|
value={'0'}
|
||||||
|
checked={schDisplayFlg === '0'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchSchDisplayFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schDisplayFlg0">{getMessage('estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio light">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="schDisplayFlg"
|
||||||
|
id="schDisplayFlg1"
|
||||||
|
value={'1'}
|
||||||
|
checked={schDisplayFlg === '1'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchSchDisplayFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schDisplayFlg1">{getMessage('estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{getMessage('estimate.detail.docPopup.schWeightFlg')} <span className="red">*</span>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="d-check-radio light mr10">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="schWeightFlg"
|
||||||
|
id="schWeightFlg0"
|
||||||
|
value={'0'}
|
||||||
|
checked={schWeightFlg === '0'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchWeightFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schWeightFlg0">{getMessage('estimate.detail.docPopup.schWeightFlg.schWeightFlg0')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio light">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="schWeightFlg"
|
||||||
|
id="schWeightFlg1"
|
||||||
|
value={'1'}
|
||||||
|
checked={schWeightFlg === '1'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchWeightFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schWeightFlg1">{getMessage('estimate.detail.docPopup.schWeightFlg.schWeightFlg1')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{getMessage('estimate.detail.docPopup.schDrawingFlg')}</th>
|
||||||
|
<td>
|
||||||
|
<div className="form-flex-wrap">
|
||||||
|
<div className="d-check-radio light mr10">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="schDrawingFlg"
|
||||||
|
id="schDrawingFlg0"
|
||||||
|
value={'0'}
|
||||||
|
checked={schDrawingFlg === '0'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchDrawingFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schDrawingFlg0">{getMessage('estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio light">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="schDrawingFlg"
|
||||||
|
id="schDrawingFlg01"
|
||||||
|
value={'1'}
|
||||||
|
checked={schDrawingFlg === '1'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchDrawingFlg(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="schDrawingFlg01">{getMessage('estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="footer-btn-wrap">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-origin grey mr5"
|
||||||
|
onClick={() => {
|
||||||
|
setEstimatePopupOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getMessage('estimate.detail.docPopup.close')}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn-origin navy" onClick={() => handleFileDown()}>
|
||||||
|
{getMessage('estimate.detail.docPopup.docDownload')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -145,6 +145,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//QPolygon 좌표 이동시 좌표 재계산
|
||||||
this.on('polygonMoved', () => {
|
this.on('polygonMoved', () => {
|
||||||
//폴리곤일때만 사용
|
//폴리곤일때만 사용
|
||||||
let matrix = this.calcTransformMatrix()
|
let matrix = this.calcTransformMatrix()
|
||||||
@ -156,8 +157,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
.map((p) => {
|
.map((p) => {
|
||||||
return fabric.util.transformPoint(p, matrix)
|
return fabric.util.transformPoint(p, matrix)
|
||||||
})
|
})
|
||||||
this.set('points', transformedPoints)
|
this.points = transformedPoints
|
||||||
this.set('pathOffset', { x: this.left, y: this.top })
|
const { left, top } = this.calcOriginCoords()
|
||||||
|
this.set('pathOffset', { x: left, y: top })
|
||||||
this.setCoords()
|
this.setCoords()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -788,7 +790,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
setViewLengthText(isView) {
|
setViewLengthText(isView) {
|
||||||
this.canvas
|
this.canvas
|
||||||
?.getObjects()
|
?.getObjects()
|
||||||
.filter((obj) => obj.name === 'lengthText' && obj.parent === this)
|
.filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
||||||
.forEach((text) => {
|
.forEach((text) => {
|
||||||
text.set({ visible: isView })
|
text.set({ visible: isView })
|
||||||
})
|
})
|
||||||
@ -801,7 +803,33 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.scaleY = scale
|
this.scaleY = scale
|
||||||
this.addLengthText()
|
this.addLengthText()
|
||||||
},
|
},
|
||||||
divideLine() {
|
|
||||||
// splitPolygonWithLines(this)
|
calcOriginCoords() {
|
||||||
|
const points = this.points
|
||||||
|
const minX = Math.min(...points.map((p) => p.x))
|
||||||
|
const maxX = Math.max(...points.map((p) => p.x))
|
||||||
|
const minY = Math.min(...points.map((p) => p.y))
|
||||||
|
const maxY = Math.max(...points.map((p) => p.y))
|
||||||
|
|
||||||
|
let left = 0
|
||||||
|
let top = 0
|
||||||
|
|
||||||
|
if (this.originX === 'center') {
|
||||||
|
left = (minX + maxX) / 2
|
||||||
|
} else if (this.originX === 'left') {
|
||||||
|
left = minX
|
||||||
|
} else if (this.originX === 'right') {
|
||||||
|
left = maxX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.originY === 'center') {
|
||||||
|
top = (minY + maxY) / 2
|
||||||
|
} else if (this.originY === 'top') {
|
||||||
|
top = minY
|
||||||
|
} else if (this.originY === 'bottom') {
|
||||||
|
top = maxY
|
||||||
|
}
|
||||||
|
|
||||||
|
return { left, top }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -33,6 +33,8 @@ import useMenu from '@/hooks/common/useMenu'
|
|||||||
import { MENU } from '@/common/common'
|
import { MENU } from '@/common/common'
|
||||||
|
|
||||||
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
||||||
|
import { estimateState } from '@/store/floorPlanObjectAtom'
|
||||||
|
import DocDownOptionPop from '../estimate/popup/DocDownOptionPop'
|
||||||
|
|
||||||
export default function CanvasMenu(props) {
|
export default function CanvasMenu(props) {
|
||||||
const { menuNumber, setMenuNumber } = props
|
const { menuNumber, setMenuNumber } = props
|
||||||
@ -53,7 +55,10 @@ export default function CanvasMenu(props) {
|
|||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
const { handleZoomClear, handleZoom } = useCanvasEvent()
|
const { handleZoomClear, handleZoom } = useCanvasEvent()
|
||||||
const { handleMenu } = useMenu()
|
const { handleMenu } = useMenu()
|
||||||
|
|
||||||
const { handleEstimateSubmit } = useEstimateController()
|
const { handleEstimateSubmit } = useEstimateController()
|
||||||
|
const estimateRecoilState = useRecoilValue(estimateState)
|
||||||
|
const [estimatePopupOpen, setEstimatePopupOpen] = useState(false)
|
||||||
|
|
||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
const { currentCanvasPlan, saveCanvas } = usePlan()
|
const { currentCanvasPlan, saveCanvas } = usePlan()
|
||||||
@ -81,6 +86,9 @@ export default function CanvasMenu(props) {
|
|||||||
case 4:
|
case 4:
|
||||||
setType('module')
|
setType('module')
|
||||||
break
|
break
|
||||||
|
case 6:
|
||||||
|
router.push(`/floor-plan/simulator/${menu.index}`)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathname !== '/floor-plan') router.push('/floor-plan')
|
if (pathname !== '/floor-plan') router.push('/floor-plan')
|
||||||
@ -135,6 +143,38 @@ export default function CanvasMenu(props) {
|
|||||||
addPopup(id, 1, <SettingModal01 id={id} />, true)
|
addPopup(id, 1, <SettingModal01 id={id} />, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 견적서 초기화 버튼
|
||||||
|
const handleEstimateReset = () => {
|
||||||
|
// console.log('estimateRecoilState::', estimateRecoilState)
|
||||||
|
//objectNo, planNo
|
||||||
|
swalFire({
|
||||||
|
//저장된 견적서 정보가 초기화되고, 도면정보가 반영됩니다. 정말로 초기화 하시겠습니까?
|
||||||
|
//물건정보
|
||||||
|
text: getMessage('estimate.detail.reset.confirmMsg'),
|
||||||
|
type: 'confirm',
|
||||||
|
confirmFn: () => {
|
||||||
|
console.log('내용초기화 및 변경일시 갱신')
|
||||||
|
},
|
||||||
|
denyFn: () => {
|
||||||
|
console.log('초기화하지 않음. 변경일시 갱신안함')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 견적서 복사버튼
|
||||||
|
* (견적서 번호(estimateRecoilState.docNo)가 생성된 이후 버튼 활성화 )
|
||||||
|
* T01관리자 계정 및 1차판매점에게만 제공
|
||||||
|
*/
|
||||||
|
|
||||||
|
const handleEstimateCopy = () => {
|
||||||
|
// console.log('estimateRecoilState::', estimateRecoilState)
|
||||||
|
//objectNo, planNo
|
||||||
|
console.log('복사')
|
||||||
|
console.log('물건정보+도면+견적서를 모두 복사')
|
||||||
|
console.log('견적서 가격은 정가를 표시')
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (globalLocale === 'ko') {
|
if (globalLocale === 'ko') {
|
||||||
setAppMessageState(KO)
|
setAppMessageState(KO)
|
||||||
@ -225,7 +265,7 @@ export default function CanvasMenu(props) {
|
|||||||
{menuNumber === 5 && (
|
{menuNumber === 5 && (
|
||||||
<>
|
<>
|
||||||
<div className="ico-btn-from">
|
<div className="ico-btn-from">
|
||||||
<button className="btn-frame gray ico-flx act">
|
<button className="btn-frame gray ico-flx" onClick={() => setEstimatePopupOpen(true)}>
|
||||||
<span className="ico ico01"></span>
|
<span className="ico ico01"></span>
|
||||||
<span>{getMessage('plan.menu.estimate.docDown')}</span>
|
<span>{getMessage('plan.menu.estimate.docDown')}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -233,11 +273,24 @@ export default function CanvasMenu(props) {
|
|||||||
<span className="ico ico02"></span>
|
<span className="ico ico02"></span>
|
||||||
<span>{getMessage('plan.menu.estimate.save')}</span>
|
<span>{getMessage('plan.menu.estimate.save')}</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="btn-frame gray ico-flx">
|
{/* {estimateRecoilState?.docNo != null && ( */}
|
||||||
|
<button
|
||||||
|
className="btn-frame gray ico-flx"
|
||||||
|
onClick={() => {
|
||||||
|
handleEstimateReset()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span className="ico ico03"></span>
|
<span className="ico ico03"></span>
|
||||||
<span>{getMessage('plan.menu.estimate.reset')}</span>
|
<span>{getMessage('plan.menu.estimate.reset')}</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="btn-frame gray ico-flx">
|
{/* )} */}
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn-frame gray ico-flx"
|
||||||
|
onClick={() => {
|
||||||
|
handleEstimateCopy()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span className="ico ico04"></span>
|
<span className="ico ico04"></span>
|
||||||
<span>{getMessage('plan.menu.estimate.copy')}</span>
|
<span>{getMessage('plan.menu.estimate.copy')}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -263,6 +316,8 @@ export default function CanvasMenu(props) {
|
|||||||
<div className={`canvas-depth2-wrap ${menuNumber === 2 || menuNumber === 3 || menuNumber === 4 ? 'active' : ''}`}>
|
<div className={`canvas-depth2-wrap ${menuNumber === 2 || menuNumber === 3 || menuNumber === 4 ? 'active' : ''}`}>
|
||||||
{(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && <MenuDepth01 />}
|
{(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && <MenuDepth01 />}
|
||||||
</div>
|
</div>
|
||||||
|
{/* 견적서(menuNumber=== 5) 상세화면인경우 문서다운로드 팝업 */}
|
||||||
|
{estimatePopupOpen && <DocDownOptionPop planNo={estimateRecoilState?.planNo} setEstimatePopupOpen={setEstimatePopupOpen} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default function SizeSetting(props) {
|
|||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
const { closePopup } = usePopup()
|
const { closePopup } = usePopup()
|
||||||
const { resizeObjectBatch } = useObjectBatch({})
|
const { resizeObjectBatch } = useObjectBatch({})
|
||||||
const { reSizePolygon } = useSurfaceShapeBatch()
|
const { resizeSurfaceShapeBatch } = useSurfaceShapeBatch()
|
||||||
const widthRef = useRef(null)
|
const widthRef = useRef(null)
|
||||||
const heightRef = useRef(null)
|
const heightRef = useRef(null)
|
||||||
|
|
||||||
@ -36,10 +36,11 @@ export default function SizeSetting(props) {
|
|||||||
target.name === BATCH_TYPE.OPENING ||
|
target.name === BATCH_TYPE.OPENING ||
|
||||||
target.name === BATCH_TYPE.SHADOW ||
|
target.name === BATCH_TYPE.SHADOW ||
|
||||||
target.name === BATCH_TYPE.TRIANGLE_DORMER ||
|
target.name === BATCH_TYPE.TRIANGLE_DORMER ||
|
||||||
target.name === BATCH_TYPE.PENTAGON_DORMER ||
|
target.name === BATCH_TYPE.PENTAGON_DORMER
|
||||||
target.name === POLYGON_TYPE.ROOF
|
|
||||||
) {
|
) {
|
||||||
resizeObjectBatch(settingTarget, target, width, height)
|
resizeObjectBatch(settingTarget, target, width, height)
|
||||||
|
} else if (target.name === POLYGON_TYPE.ROOF) {
|
||||||
|
resizeSurfaceShapeBatch(settingTarget, target, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
|
import { useEffect, useState } from 'react'
|
||||||
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
|
|
||||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
|
||||||
import { useRecoilState } from 'recoil'
|
import { useRecoilState } from 'recoil'
|
||||||
import { Fragment, useEffect, useState } from 'react'
|
|
||||||
import { canvasSettingState } from '@/store/canvasAtom'
|
import { canvasSettingState } from '@/store/canvasAtom'
|
||||||
|
import { basicSettingState } from '@/store/settingAtom'
|
||||||
|
|
||||||
import { useMessage } from '@/hooks/useMessage'
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
import { useAxios } from '@/hooks/useAxios'
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
import { useSwal } from '@/hooks/useSwal'
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
import { usePopup } from '@/hooks/usePopup'
|
import { usePopup } from '@/hooks/usePopup'
|
||||||
import { basicSettingState } from '@/store/settingAtom'
|
import useRefFiles from '@/hooks/common/useRefFiles'
|
||||||
|
import { usePlan } from '@/hooks/usePlan'
|
||||||
|
|
||||||
|
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
|
||||||
|
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
|
||||||
|
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||||
|
import { SessionContext } from '@/app/SessionProvider'
|
||||||
|
|
||||||
export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) {
|
export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) {
|
||||||
const [objectNo, setObjectNo] = useState('test123241008001') // 후에 삭제 필요
|
const [objectNo, setObjectNo] = useState('test123241008001') // 후에 삭제 필요
|
||||||
@ -18,7 +24,20 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
|
|||||||
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
|
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
|
||||||
const { closePopup } = usePopup()
|
const { closePopup } = usePopup()
|
||||||
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
|
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
|
||||||
const [image, setImage] = useState(null)
|
const {
|
||||||
|
refImage,
|
||||||
|
queryRef,
|
||||||
|
setRefImage,
|
||||||
|
handleRefFile,
|
||||||
|
refFileMethod,
|
||||||
|
setRefFileMethod,
|
||||||
|
handleRefFileMethod,
|
||||||
|
mapPositionAddress,
|
||||||
|
setMapPositionAddress,
|
||||||
|
handleFileDelete,
|
||||||
|
handleMapImageDown,
|
||||||
|
} = useRefFiles()
|
||||||
|
const { currentCanvasPlan } = usePlan()
|
||||||
|
|
||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
const { get, post } = useAxios()
|
const { get, post } = useAxios()
|
||||||
@ -487,19 +506,85 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{getMessage('common.input.file')}</th>
|
<th>{getMessage('common.input.file')}</th>
|
||||||
<td>
|
<td>
|
||||||
<div className="flex-box">
|
<div className="pop-form-radio mb10">
|
||||||
|
<div className="d-check-radio pop">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio03"
|
||||||
|
id="ra06"
|
||||||
|
value={'1'}
|
||||||
|
onChange={(e) => handleRefFileMethod(e)}
|
||||||
|
checked={refFileMethod === '1'}
|
||||||
|
/>
|
||||||
|
<label htmlFor="ra06">ファイルを読み込む</label>
|
||||||
|
</div>
|
||||||
|
<div className="d-check-radio pop">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio03"
|
||||||
|
id="ra07"
|
||||||
|
value={'2'}
|
||||||
|
onChange={(e) => handleRefFileMethod(e)}
|
||||||
|
checked={refFileMethod === '2'}
|
||||||
|
/>
|
||||||
|
<label htmlFor="ra07">アドレスを読み込む</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 파일 불러오기 */}
|
||||||
|
{refFileMethod === '1' && (
|
||||||
|
<div className="flex-box">
|
||||||
|
<div className="img-edit-wrap">
|
||||||
|
<label className="img-edit-btn" htmlFor="img_file">
|
||||||
|
<span className="img-edit"></span>
|
||||||
|
{getMessage('common.input.file.load')}
|
||||||
|
</label>
|
||||||
|
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
|
||||||
|
</div>
|
||||||
|
<div className="img-name-wrap">
|
||||||
|
{currentCanvasPlan?.bgImageName === null ? (
|
||||||
|
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
|
||||||
|
) : (
|
||||||
|
<input type="text" className="input-origin al-l" value={currentCanvasPlan?.bgImageName} readOnly />
|
||||||
|
)}
|
||||||
|
{(refImage || currentCanvasPlan?.bgImageName) && <button className="img-check" onClick={handleFileDelete}></button>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 주소 불러오기 */}
|
||||||
|
{refFileMethod === '2' && (
|
||||||
|
<div className="flex-box for-address">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-origin al-l mr10"
|
||||||
|
placeholder={'住所入力'}
|
||||||
|
ref={queryRef}
|
||||||
|
value={mapPositionAddress}
|
||||||
|
onChange={(e) => setMapPositionAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="img-edit-wrap mr5">
|
||||||
|
<button className="img-edit-btn" onClick={handleMapImageDown}>
|
||||||
|
完了
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{mapPositionAddress && <span className="check-address fail"></span>}
|
||||||
|
{/* <span className="check-address success"></span> */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* <div className="flex-box">
|
||||||
<div className="img-edit-wrap">
|
<div className="img-edit-wrap">
|
||||||
<label className="img-edit-btn" htmlFor="img_file">
|
<label className="img-edit-btn" htmlFor="img_file">
|
||||||
<span className="img-edit"></span>
|
<span className="img-edit"></span>
|
||||||
{getMessage('common.input.file.load')}
|
{getMessage('common.input.file.load')}
|
||||||
</label>
|
</label>
|
||||||
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => setImage(e.target.files[0])} />
|
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
|
||||||
</div>
|
</div>
|
||||||
<div className="img-name-wrap">
|
<div className="img-name-wrap">
|
||||||
<input type="text" className="input-origin al-l" defaultValue={''} value={image ? image.name : ''} readOnly />
|
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
|
||||||
{image && <button className="img-check" onClick={() => setImage(null)}></button>}
|
{refImage && <button className="img-check" onClick={() => setRefImage(null)}></button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
365
src/components/simulator/Simulator.jsx
Normal file
365
src/components/simulator/Simulator.jsx
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import 'chart.js/auto'
|
||||||
|
import { Bar } from 'react-chartjs-2'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
import { useEffect, useState, useRef } from 'react'
|
||||||
|
import { useRecoilValue } from 'recoil'
|
||||||
|
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||||
|
|
||||||
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
|
import { usePlan } from '@/hooks/usePlan'
|
||||||
|
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||||
|
|
||||||
|
import { convertNumberToPriceDecimal } from '@/util/common-utils'
|
||||||
|
|
||||||
|
export default function Simulator() {
|
||||||
|
const { plans } = usePlan()
|
||||||
|
const plan = plans.find((plan) => plan.isCurrent === true)
|
||||||
|
|
||||||
|
const chartRef = useRef(null)
|
||||||
|
|
||||||
|
// recoil 물건번호
|
||||||
|
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||||
|
const [objectNo, setObjectNo] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setObjectNo(objectRecoil.floorPlanObjectNo)
|
||||||
|
}, [objectRecoil])
|
||||||
|
|
||||||
|
// 캔버스 메뉴 넘버 셋팅
|
||||||
|
const { setMenuNumber } = useCanvasMenu()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMenuNumber(6)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { get } = useAxios()
|
||||||
|
const { getMessage } = useMessage()
|
||||||
|
|
||||||
|
// 차트 관련
|
||||||
|
const [chartData, setChartData] = useState([])
|
||||||
|
const data = {
|
||||||
|
labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'kWh',
|
||||||
|
data: chartData.slice(0, 12),
|
||||||
|
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.2)',
|
||||||
|
'rgba(54, 162, 235, 0.2)',
|
||||||
|
'rgba(255, 206, 86, 0.2)',
|
||||||
|
'rgba(75, 192, 192, 0.2)',
|
||||||
|
'rgba(153, 102, 255, 0.2)',
|
||||||
|
'rgba(255, 159, 64, 0.2)',
|
||||||
|
'rgba(0, 99, 132, 0.2)',
|
||||||
|
'rgba(0, 162, 235, 0.2)',
|
||||||
|
'rgba(0, 206, 86, 0.2)',
|
||||||
|
'rgba(0, 192, 192, 0.2)',
|
||||||
|
'rgba(0, 102, 255, 0.2)',
|
||||||
|
'rgba(0, 159, 64, 0.2)',
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(255, 99, 132, 0.2)',
|
||||||
|
'rgba(54, 162, 235, 0.2)',
|
||||||
|
'rgba(255, 206, 86, 0.2)',
|
||||||
|
'rgba(75, 192, 192, 0.2)',
|
||||||
|
'rgba(153, 102, 255, 0.2)',
|
||||||
|
'rgba(255, 159, 64, 0.2)',
|
||||||
|
'rgba(0, 99, 132, 0.2)',
|
||||||
|
'rgba(0, 162, 235, 0.2)',
|
||||||
|
'rgba(0, 206, 86, 0.2)',
|
||||||
|
'rgba(0, 192, 192, 0.2)',
|
||||||
|
'rgba(0, 102, 255, 0.2)',
|
||||||
|
'rgba(0, 159, 64, 0.2)',
|
||||||
|
],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (objectNo) {
|
||||||
|
fetchObjectDetail(objectNo)
|
||||||
|
}
|
||||||
|
fetchSimulatorNotice()
|
||||||
|
}, [objectNo, plan])
|
||||||
|
|
||||||
|
// 물건 상세 정보 조회
|
||||||
|
const [objectDetail, setObjectDetail] = useState({})
|
||||||
|
|
||||||
|
// 모듈배치정보 조회
|
||||||
|
const [moduleInfoList, setModuleInfoList] = useState([])
|
||||||
|
|
||||||
|
// 파워컨디셔너 조회
|
||||||
|
const [pcsInfoList, setPcsInfoList] = useState([])
|
||||||
|
|
||||||
|
const fetchObjectDetail = async (objectNo) => {
|
||||||
|
const apiUrl = `/api/pwrGnrSimulation/calculations?objectNo=${objectNo}&planNo=${plan?.id}`
|
||||||
|
const resultData = await get({ url: apiUrl })
|
||||||
|
if (resultData) {
|
||||||
|
setObjectDetail(resultData)
|
||||||
|
if (resultData.frcPwrGnrList) {
|
||||||
|
setChartData(resultData.frcPwrGnrList)
|
||||||
|
}
|
||||||
|
if (resultData.pcsList) {
|
||||||
|
setPcsInfoList(resultData.pcsList)
|
||||||
|
}
|
||||||
|
if (resultData.roofModuleList) {
|
||||||
|
setModuleInfoList(resultData.roofModuleList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시뮬레이션 안내사항 조회
|
||||||
|
const [content, setContent] = useState('')
|
||||||
|
|
||||||
|
const fetchSimulatorNotice = async () => {
|
||||||
|
get({ url: '/api/pwrGnrSimulation/guideInfo' }).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
setContent(res.data.replaceAll('\n', '<br/>'))
|
||||||
|
} else {
|
||||||
|
setContent(getMessage('common.message.no.data'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sub-content estimate">
|
||||||
|
<div className="sub-content-inner">
|
||||||
|
<div className="sub-content-box">
|
||||||
|
<div className="sub-table-box">
|
||||||
|
<div className="estimate-list-wrap">
|
||||||
|
{/* 물건번호 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub1')}</div>
|
||||||
|
<div className="estimate-name">
|
||||||
|
{objectDetail.objectNo} (Plan No: {objectDetail.planNo})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 작성일 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub2')}</div>
|
||||||
|
<div className="estimate-name">{`${dayjs(objectDetail.drawingEstimateCreateDate).format('YYYY.MM.DD')}`}</div>
|
||||||
|
</div>
|
||||||
|
{/* 시스템용량 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub3')}</div>
|
||||||
|
<div className="estimate-name">{convertNumberToPriceDecimal(objectDetail.capacity)}kW</div>
|
||||||
|
</div>
|
||||||
|
{/* 연간예측발전량 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub4')}</div>
|
||||||
|
<div className="estimate-name">{convertNumberToPriceDecimal(objectDetail.anlFrcsGnrt)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="estimate-list-wrap">
|
||||||
|
{/* 도도부현 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub5')}</div>
|
||||||
|
<div className="estimate-name">{objectDetail.prefName}</div>
|
||||||
|
</div>
|
||||||
|
{/* 일사량 관측지점 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub6')}</div>
|
||||||
|
<div className="estimate-name">{objectDetail.areaName}</div>
|
||||||
|
</div>
|
||||||
|
{/* 적설조건 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub7')}</div>
|
||||||
|
<div className="estimate-name">{objectDetail.snowfall}</div>
|
||||||
|
</div>
|
||||||
|
{/* 풍속조건 */}
|
||||||
|
<div className="estimate-box">
|
||||||
|
<div className="estimate-tit">{getMessage('simulator.title.sub8')}</div>
|
||||||
|
<div className="estimate-name">{objectDetail.standardWindSpeedId}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="sub-content-box">
|
||||||
|
<div className="chart-wrap">
|
||||||
|
<div className="chart-inner">
|
||||||
|
<div className="sub-table-box">
|
||||||
|
<div className="chart-box">
|
||||||
|
<Bar ref={chartRef} data={data} options={options} />
|
||||||
|
</div>
|
||||||
|
<div className="table-box-title-wrap">
|
||||||
|
<div className="title-wrap">
|
||||||
|
<h3>{getMessage('simulator.table.sub9')} </h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 예측발전량 */}
|
||||||
|
<div className="chart-month-table">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>1月</th>
|
||||||
|
<th>2月</th>
|
||||||
|
<th>3月</th>
|
||||||
|
<th>4月</th>
|
||||||
|
<th>5月</th>
|
||||||
|
<th>6月</th>
|
||||||
|
<th>7月</th>
|
||||||
|
<th>8月</th>
|
||||||
|
<th>9月</th>
|
||||||
|
<th>10月</th>
|
||||||
|
<th>11月</th>
|
||||||
|
<th>12月</th>
|
||||||
|
<th>{getMessage('simulator.table.sub6')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{chartData.length > 0 ? (
|
||||||
|
<tr>
|
||||||
|
{chartData.map((data) => (
|
||||||
|
<td key={data}>{convertNumberToPriceDecimal(data)}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={13}>{getMessage('common.message.no.data')}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="chart-table-wrap">
|
||||||
|
<div className="sub-table-box">
|
||||||
|
<div className="module-table ">
|
||||||
|
<table className="small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{getMessage('simulator.table.sub1')}</th>
|
||||||
|
<th>{getMessage('simulator.table.sub2')}</th>
|
||||||
|
<th>{getMessage('simulator.table.sub3')}</th>
|
||||||
|
<th>{getMessage('simulator.table.sub4')}</th>
|
||||||
|
<th>{getMessage('simulator.table.sub5')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{moduleInfoList.length > 0 ? (
|
||||||
|
moduleInfoList.map((moduleInfo) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr key={moduleInfo.itemId}>
|
||||||
|
{/* 지붕면 */}
|
||||||
|
<td>{moduleInfo.roofSurface}</td>
|
||||||
|
{/* 경사각 */}
|
||||||
|
<td>{convertNumberToPriceDecimal(moduleInfo.slope)}寸</td>
|
||||||
|
{/* 방위각(도) */}
|
||||||
|
<td>{convertNumberToPriceDecimal(moduleInfo.angle)}</td>
|
||||||
|
{/* 태양전지모듈 */}
|
||||||
|
<td>
|
||||||
|
<div className="overflow-lab">{moduleInfo.itemNo}</div>
|
||||||
|
</td>
|
||||||
|
{/* 매수 */}
|
||||||
|
<td>{moduleInfo.amount}</td>
|
||||||
|
</tr>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={5}>{getMessage('common.message.no.data')}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{moduleInfoList.length > 0 && (
|
||||||
|
<div className="module-total">
|
||||||
|
<div className="total-title">{getMessage('simulator.table.sub6')}</div>
|
||||||
|
<div className="total-num">
|
||||||
|
{moduleInfoList.reduce((acc, moduleInfo) => convertNumberToPriceDecimal(Number(acc) + Number(moduleInfo.amount)), 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="sub-table-box">
|
||||||
|
<div className="module-table ">
|
||||||
|
<table className="big">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{getMessage('simulator.table.sub7')}</th>
|
||||||
|
<th>{getMessage('simulator.table.sub8')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{pcsInfoList.length > 0 ? (
|
||||||
|
pcsInfoList.map((pcsInfo) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr key={pcsInfo.itemId}>
|
||||||
|
{/* 파워컨디셔너 */}
|
||||||
|
<td className="al-l">
|
||||||
|
<div className="overflow-lab">{pcsInfo.itemNo}</div>
|
||||||
|
</td>
|
||||||
|
{/* 대 */}
|
||||||
|
<td>{pcsInfo.amount}</td>
|
||||||
|
</tr>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={2}>{getMessage('common.message.no.data')}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="sub-content-box">
|
||||||
|
<div className="sub-table-box">
|
||||||
|
<div className="simulation-guide-wrap">
|
||||||
|
<div className="simulation-tit-wrap">
|
||||||
|
<span>
|
||||||
|
{getMessage('simulator.notice.sub1')}
|
||||||
|
<br />
|
||||||
|
{getMessage('simulator.notice.sub2')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/* 시뮬레이션 안내사항 */}
|
||||||
|
<div
|
||||||
|
className="simulation-guide-box"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: content,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import { useFont } from '@/hooks/common/useFont'
|
|||||||
import { useGrid } from '@/hooks/common/useGrid'
|
import { useGrid } from '@/hooks/common/useGrid'
|
||||||
import { globalFontAtom } from '@/store/fontAtom'
|
import { globalFontAtom } from '@/store/fontAtom'
|
||||||
import { useRoof } from '@/hooks/common/useRoof'
|
import { useRoof } from '@/hooks/common/useRoof'
|
||||||
|
import { usePolygon } from '@/hooks/usePolygon'
|
||||||
|
|
||||||
export function useCanvasConfigInitialize() {
|
export function useCanvasConfigInitialize() {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
@ -20,6 +21,7 @@ export function useCanvasConfigInitialize() {
|
|||||||
const {} = useFont()
|
const {} = useFont()
|
||||||
const {} = useGrid()
|
const {} = useGrid()
|
||||||
const {} = useRoof()
|
const {} = useRoof()
|
||||||
|
const { drawDirectionArrow } = usePolygon()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
@ -173,6 +175,7 @@ export function useCanvasConfigInitialize() {
|
|||||||
|
|
||||||
//그룹 객체로 다시 만든다 (좌표때문에)
|
//그룹 객체로 다시 만든다 (좌표때문에)
|
||||||
const group = new fabric.Group(objectArray, {
|
const group = new fabric.Group(objectArray, {
|
||||||
|
...objectArray,
|
||||||
groupId: id,
|
groupId: id,
|
||||||
name: objectsName,
|
name: objectsName,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
|
|||||||
112
src/hooks/common/useRefFiles.js
Normal file
112
src/hooks/common/useRefFiles.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useRecoilState } from 'recoil'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
|
import { convertDwgToPng } from '@/lib/cadAction'
|
||||||
|
import { useAxios } from '../useAxios'
|
||||||
|
import { currentCanvasPlanState } from '@/store/canvasAtom'
|
||||||
|
|
||||||
|
export default function useRefFiles() {
|
||||||
|
const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL
|
||||||
|
const [refImage, setRefImage] = useState(null)
|
||||||
|
const [refFileMethod, setRefFileMethod] = useState('1')
|
||||||
|
const [mapPositionAddress, setMapPositionAddress] = useState('')
|
||||||
|
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
|
||||||
|
const queryRef = useRef(null)
|
||||||
|
|
||||||
|
const { swalFire } = useSwal()
|
||||||
|
const { get, promisePut } = useAxios()
|
||||||
|
// const { currentCanvasPlan, setCurrentCanvasPlan } = usePlan()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 파일 불러오기 버튼 컨트롤
|
||||||
|
* @param {*} file
|
||||||
|
*/
|
||||||
|
const handleRefFile = (file) => {
|
||||||
|
setRefImage(file)
|
||||||
|
file.name.split('.').pop() === 'dwg' ? handleUploadRefFile(file) : () => {}
|
||||||
|
// handleUploadRefFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 파일 삭제
|
||||||
|
*/
|
||||||
|
const handleFileDelete = () => {
|
||||||
|
setRefImage(null)
|
||||||
|
setCurrentCanvasPlan((prev) => ({ ...prev, bgFileName: null }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주소 삭제
|
||||||
|
*/
|
||||||
|
const handleAddressDelete = () => {
|
||||||
|
setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주소로 구글 맵 이미지 다운로드
|
||||||
|
*/
|
||||||
|
const handleMapImageDown = async () => {
|
||||||
|
if (queryRef.current.value === '' || queryRef.current.value === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await get({ url: `http://localhost:3000/api/html2canvas?q=${queryRef.current.value}&fileNm=${uuidv4()}&zoom=20` })
|
||||||
|
console.log('🚀 ~ handleMapImageDown ~ res:', res)
|
||||||
|
setCurrentCanvasPlan((prev) => ({ ...prev, bgFileName: res.fileNm, mapPositionAddress: queryRef.current.value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 플랜이 변경되면 플랜 상태 저장
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const handleCurrentPlan = async () => {
|
||||||
|
await promisePut({ url: '/api/canvas-management/canvas-statuses', data: currentCanvasPlan }).then((res) => {
|
||||||
|
console.log('🚀 ~ awaitpromisePut ~ res:', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
handleCurrentPlan()
|
||||||
|
}, [currentCanvasPlan])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RefFile이 캐드 도면 파일일 경우 변환하여 이미지로 저장
|
||||||
|
* @param {*} file
|
||||||
|
*/
|
||||||
|
const handleUploadRefFile = async (file) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
|
||||||
|
await promisePost({ url: converterUrl, data: formData })
|
||||||
|
.then((res) => {
|
||||||
|
convertDwgToPng(res.data.Files[0].FileName, res.data.Files[0].FileData)
|
||||||
|
swalFire({ text: '파일 변환 성공' })
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
swalFire({ text: '파일 변환 실패', icon: 'error' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 라디오 버튼 컨트롤
|
||||||
|
* @param {*} e
|
||||||
|
*/
|
||||||
|
const handleRefFileMethod = (e) => {
|
||||||
|
setRefFileMethod(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
refImage,
|
||||||
|
queryRef,
|
||||||
|
setRefImage,
|
||||||
|
handleRefFile,
|
||||||
|
refFileMethod,
|
||||||
|
setRefFileMethod,
|
||||||
|
mapPositionAddress,
|
||||||
|
setMapPositionAddress,
|
||||||
|
handleRefFileMethod,
|
||||||
|
handleFileDelete,
|
||||||
|
handleAddressDelete,
|
||||||
|
handleMapImageDown,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
|
|||||||
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
||||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||||
import { SessionContext } from '@/app/SessionProvider'
|
import { SessionContext } from '@/app/SessionProvider'
|
||||||
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
|
|
||||||
const reducer = (prevState, nextState) => {
|
const reducer = (prevState, nextState) => {
|
||||||
return { ...prevState, ...nextState }
|
return { ...prevState, ...nextState }
|
||||||
@ -21,35 +22,33 @@ const defaultEstimateData = {
|
|||||||
estimateType: 'YJOD', //주문분류
|
estimateType: 'YJOD', //주문분류
|
||||||
remarks: '', //비고
|
remarks: '', //비고
|
||||||
estimateOption: '', //견적특이사항
|
estimateOption: '', //견적특이사항
|
||||||
// itemList: [{ id: 1, name: '' }],
|
|
||||||
//아이템에 필요없는거 빼기
|
//아이템에 필요없는거 빼기
|
||||||
itemList: [
|
itemList: [
|
||||||
{
|
// {
|
||||||
amount: '',
|
// amount: '',
|
||||||
fileUploadFlg: '',
|
// fileUploadFlg: '',
|
||||||
itemChangeFlg: '',
|
// itemChangeFlg: '',
|
||||||
itemGroup: '',
|
// itemGroup: '',
|
||||||
itemId: '', //키값??
|
// itemId: '', //키값??
|
||||||
itemName: '',
|
// itemName: '',
|
||||||
itemNo: '',
|
// itemNo: '',
|
||||||
moduleFlg: '',
|
// moduleFlg: '',
|
||||||
objectNo: '',
|
// objectNo: '',
|
||||||
pkgMaterialFlg: '',
|
// pkgMaterialFlg: '',
|
||||||
planNo: '',
|
// planNo: '',
|
||||||
pnowW: '',
|
// pnowW: '',
|
||||||
salePrice: '',
|
// salePrice: '',
|
||||||
saleTotPrice: '',
|
// saleTotPrice: '',
|
||||||
specification: '',
|
// specification: '',
|
||||||
unit: '',
|
// unit: '',
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
fileList: [],
|
fileList: [],
|
||||||
|
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
// const updateItemInList = (itemList, id, updates) => {
|
|
||||||
const updateItemInList = (itemList, itemId, updates) => {
|
const updateItemInList = (itemList, itemId, updates) => {
|
||||||
// return itemList.map((item) => (item.id === id ? { ...item, ...updates } : item))
|
|
||||||
return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item))
|
return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +58,8 @@ export const useEstimateController = (planNo) => {
|
|||||||
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
||||||
const [estimateData, setEstimateData] = useRecoilState(estimateState)
|
const [estimateData, setEstimateData] = useRecoilState(estimateState)
|
||||||
|
|
||||||
|
const { getMessage } = useMessage()
|
||||||
|
|
||||||
const { get, post, promisePost } = useAxios(globalLocaleState)
|
const { get, post, promisePost } = useAxios(globalLocaleState)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@ -87,20 +88,15 @@ export const useEstimateController = (planNo) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const updateItem = (id, updates) => {
|
|
||||||
const updateItem = (itemId, updates) => {
|
const updateItem = (itemId, updates) => {
|
||||||
setState({
|
setState({
|
||||||
// itemList: updateItemInList(state.itemList, id, updates),
|
|
||||||
itemList: updateItemInList(state.itemList, itemId, updates),
|
itemList: updateItemInList(state.itemList, itemId, updates),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addItem = () => {
|
const addItem = () => {
|
||||||
// const newId = Math.max(...state.itemList.map((item) => item.id)) + 1
|
|
||||||
const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1
|
const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1
|
||||||
setState({
|
setState({
|
||||||
// itemList: [...state.itemList, { id: newId, name: '' }],
|
|
||||||
//셋팅할필요없는거 빼기
|
|
||||||
itemList: [
|
itemList: [
|
||||||
...state.itemList,
|
...state.itemList,
|
||||||
{
|
{
|
||||||
@ -126,42 +122,56 @@ export const useEstimateController = (planNo) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEstimateData({ ...state, userId: session.userId })
|
setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd: session.custCd })
|
||||||
//sapSalesStoreCd 추가예정 필수값
|
|
||||||
// setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd : session.sapSalesStoreCd })
|
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
//견적서 저장
|
//견적서 저장
|
||||||
const handleEstimateSubmit = async () => {
|
const handleEstimateSubmit = async () => {
|
||||||
|
//0. 필수체크
|
||||||
|
let flag = true
|
||||||
console.log('::담긴 estimateData:::', estimateData)
|
console.log('::담긴 estimateData:::', estimateData)
|
||||||
//1. 첨부파일 저장
|
//아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼
|
||||||
const formData = new FormData()
|
if (estimateData.itemList.length > 1) {
|
||||||
formData.append('file', estimateData.fileList)
|
estimateData.itemList.map((row) => {
|
||||||
formData.append('objectNo', estimateData.objectNo)
|
if (row.fileUploadFlg === '1') {
|
||||||
formData.append('planNo', estimateData.planNo)
|
if (estimateData.fileFlg === '0') {
|
||||||
formData.append('category', '10')
|
alert(getMessage('estimate.detail.save.requiredMsg'))
|
||||||
formData.append('userId', estimateData.userId)
|
flag = false
|
||||||
for (const value of formData.values()) {
|
}
|
||||||
console.log('formData::', value)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await promisePost({ url: '/api/file/fileUpload', data: formData }).then((res) => {
|
|
||||||
console.log('파일저장::::::::::', res)
|
|
||||||
})
|
|
||||||
|
|
||||||
//2. 상세데이터 저장
|
|
||||||
|
|
||||||
console.log('상세저장시작!!')
|
|
||||||
return
|
|
||||||
try {
|
|
||||||
const result = await promisePost({
|
|
||||||
url: ESTIMATE_API_ENDPOINT,
|
|
||||||
data: estimateData,
|
|
||||||
})
|
})
|
||||||
return result
|
}
|
||||||
} catch (error) {
|
if (flag) {
|
||||||
console.error('Failed to submit estimate:', error)
|
//1. 첨부파일 저장
|
||||||
throw error
|
const formData = new FormData()
|
||||||
|
formData.append('file', estimateData.fileList)
|
||||||
|
formData.append('objectNo', estimateData.objectNo)
|
||||||
|
formData.append('planNo', estimateData.planNo)
|
||||||
|
formData.append('category', '10')
|
||||||
|
formData.append('userId', estimateData.userId)
|
||||||
|
for (const value of formData.values()) {
|
||||||
|
console.log('formData::', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
await promisePost({ url: '/api/file/fileUpload', data: formData }).then((res) => {
|
||||||
|
console.log('파일저장결과::::::::::', res)
|
||||||
|
})
|
||||||
|
|
||||||
|
//2. 상세데이터 저장
|
||||||
|
|
||||||
|
console.log('상세저장시작!!')
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
const result = await promisePost({
|
||||||
|
url: ESTIMATE_API_ENDPOINT,
|
||||||
|
data: estimateData,
|
||||||
|
})
|
||||||
|
alert(getMessage('estimate.detail.save.alertMsg'))
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to submit estimate:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -374,6 +374,9 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer))
|
const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer))
|
||||||
const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
|
const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface)
|
||||||
|
|
||||||
|
console.log('trianglePolygon', trianglePolygon)
|
||||||
|
console.log('selectedSurfacePolygon', selectedSurfacePolygon)
|
||||||
|
|
||||||
//지붕 밖으로 그렸을때
|
//지붕 밖으로 그렸을때
|
||||||
if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) {
|
if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) {
|
||||||
swalFire({ text: '도머를 배치할 수 없습니다.', icon: 'error' })
|
swalFire({ text: '도머를 배치할 수 없습니다.', icon: 'error' })
|
||||||
@ -496,6 +499,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
})
|
})
|
||||||
canvas?.add(objectGroup)
|
canvas?.add(objectGroup)
|
||||||
|
|
||||||
|
objectGroup.getObjects().forEach((obj, index) => {
|
||||||
|
console.log(`최초 pathOffset ${index}`, obj.get('pathOffset'))
|
||||||
|
})
|
||||||
|
|
||||||
isDown = false
|
isDown = false
|
||||||
initEvent()
|
initEvent()
|
||||||
dbClickEvent()
|
dbClickEvent()
|
||||||
@ -917,20 +924,17 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
originY: 'center',
|
originY: 'center',
|
||||||
left: changedCoords.x,
|
left: changedCoords.x,
|
||||||
top: changedCoords.y,
|
top: changedCoords.y,
|
||||||
|
width: width / 10,
|
||||||
|
height: height / 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (target.name === 'roof') {
|
//얘는 일단 도머에 적용함
|
||||||
//얘는 일단 도머에 적용함
|
if (target.type === 'group') {
|
||||||
if (target.type === 'group') {
|
target._objects.forEach((obj) => setSurfaceShapePattern(obj))
|
||||||
target._objects.forEach((obj) => setSurfaceShapePattern(obj))
|
|
||||||
} else {
|
|
||||||
setSurfaceShapePattern(target)
|
|
||||||
target.fire('modified')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// target.setCoords()
|
// target.setCoords()
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
|
|
||||||
if (target.type === 'group') reGroupObject(target)
|
if (target.type === 'group') reGroupObject(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
|
|||||||
import { useMessage } from '@/hooks/useMessage'
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
import { useAxios } from '@/hooks/useAxios'
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
import { useSwal } from '@/hooks/useSwal'
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
import { settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
|
import { corridorDimensionSelector, settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
|
||||||
import { setSurfaceShapePattern } from '@/util/canvas-util'
|
import { setSurfaceShapePattern } from '@/util/canvas-util'
|
||||||
import { POLYGON_TYPE } from '@/common/common'
|
import { POLYGON_TYPE } from '@/common/common'
|
||||||
|
|
||||||
@ -17,6 +17,8 @@ export function useCanvasSetting() {
|
|||||||
const { option1, option2, dimensionDisplay } = settingModalFirstOptions
|
const { option1, option2, dimensionDisplay } = settingModalFirstOptions
|
||||||
const { option3, option4 } = settingModalSecondOptions
|
const { option3, option4 } = settingModalSecondOptions
|
||||||
|
|
||||||
|
const corridorDimension = useRecoilValue(corridorDimensionSelector)
|
||||||
|
|
||||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||||
const { get, post } = useAxios(globalLocaleState)
|
const { get, post } = useAxios(globalLocaleState)
|
||||||
const { getMessage } = useMessage()
|
const { getMessage } = useMessage()
|
||||||
@ -27,6 +29,36 @@ export function useCanvasSetting() {
|
|||||||
|
|
||||||
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요
|
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { column } = corridorDimension
|
||||||
|
const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText')
|
||||||
|
switch (column) {
|
||||||
|
case 'corridorDimension':
|
||||||
|
lengthTexts.forEach((obj) => {
|
||||||
|
if (obj.planeSize) {
|
||||||
|
obj.set({ text: obj.planeSize.toString() })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'realDimension':
|
||||||
|
lengthTexts.forEach((obj) => {
|
||||||
|
if (obj.actualSize) {
|
||||||
|
obj.set({ text: obj.actualSize.toString() })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'noneDimension':
|
||||||
|
lengthTexts.forEach((obj) => {
|
||||||
|
obj.set({ text: '' })
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
canvas.renderAll()
|
||||||
|
}, [corridorDimension])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('useCanvasSetting useEffect 실행1')
|
console.log('useCanvasSetting useEffect 실행1')
|
||||||
fetchSettings()
|
fetchSettings()
|
||||||
@ -257,7 +289,7 @@ export function useCanvasSetting() {
|
|||||||
optionName = ['7']
|
optionName = ['7']
|
||||||
break
|
break
|
||||||
case 'flowDisplay': //흐름방향 표시
|
case 'flowDisplay': //흐름방향 표시
|
||||||
optionName = ['arrow']
|
optionName = ['arrow', 'flowText']
|
||||||
break
|
break
|
||||||
case 'trestleDisplay': //가대 표시
|
case 'trestleDisplay': //가대 표시
|
||||||
optionName = ['8']
|
optionName = ['8']
|
||||||
|
|||||||
@ -657,7 +657,7 @@ export function useSurfaceShapeBatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAllRelatedObjects(id) {
|
function getAllRelatedObjects(id) {
|
||||||
const result = []
|
const ult = []
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
|
|
||||||
// Create a map of objects by their id
|
// Create a map of objects by their id
|
||||||
@ -688,52 +688,8 @@ export function useSurfaceShapeBatch() {
|
|||||||
const roof = canvas.getActiveObject()
|
const roof = canvas.getActiveObject()
|
||||||
|
|
||||||
if (roof) {
|
if (roof) {
|
||||||
let isDragging = false
|
|
||||||
|
|
||||||
const childrenObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id)
|
const childrenObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id)
|
||||||
|
|
||||||
console.log('childrenObjects', childrenObjects)
|
|
||||||
|
|
||||||
// const groupObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id && obj.type === 'group')
|
|
||||||
|
|
||||||
// const ungroupObjects = [] // 그룹 해제된 객체들
|
|
||||||
// const groupChildObjects = []
|
|
||||||
|
|
||||||
// groupObjects.forEach((obj) => {
|
|
||||||
// obj._restoreObjectsState()
|
|
||||||
// obj.getObjects().forEach((o) => {
|
|
||||||
// o.set({
|
|
||||||
// ungroupYn: true,
|
|
||||||
// })
|
|
||||||
// canvas.add(o)
|
|
||||||
// ungroupObjects.push(o)
|
|
||||||
// })
|
|
||||||
// canvas.remove(obj)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const childObjects = findAllChildren(roof.id)
|
|
||||||
// groupObjects.forEach((obj) => {
|
|
||||||
// groupChildObjects.push(...obj.getObjects())
|
|
||||||
// })
|
|
||||||
|
|
||||||
// console.log('ungroupObjects', ungroupObjects)
|
|
||||||
// console.log('childObjects', childObjects)
|
|
||||||
// console.log('groupChildObjects', groupChildObjects)
|
|
||||||
|
|
||||||
// const children = canvas.getObjects().filter((obj) => obj.parentId === roof.id)
|
|
||||||
// let grandChildren = []
|
|
||||||
|
|
||||||
// children.forEach((child) => {
|
|
||||||
// if (child.type === 'group') {
|
|
||||||
// child.getObjects().forEach((grandChild) => {
|
|
||||||
// const groupObjects = canvas.getObjects().filter((obj) => obj.parentId === grandChild.id)
|
|
||||||
// grandChildren.push(...groupObjects)
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// grandChildren.push(...canvas.getObjects().filter((obj) => obj.parentId === child.id))
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
const selectionArray = [roof, ...childrenObjects]
|
const selectionArray = [roof, ...childrenObjects]
|
||||||
|
|
||||||
const selection = new fabric.ActiveSelection(selectionArray, {
|
const selection = new fabric.ActiveSelection(selectionArray, {
|
||||||
@ -748,7 +704,6 @@ export function useSurfaceShapeBatch() {
|
|||||||
canvas.setActiveObject(selection)
|
canvas.setActiveObject(selection)
|
||||||
|
|
||||||
addCanvasMouseEventListener('mouse:up', (e) => {
|
addCanvasMouseEventListener('mouse:up', (e) => {
|
||||||
isDragging = false
|
|
||||||
canvas.selection = true
|
canvas.selection = true
|
||||||
|
|
||||||
canvas.discardActiveObject() // 모든 선택 해제
|
canvas.discardActiveObject() // 모든 선택 해제
|
||||||
@ -768,7 +723,6 @@ export function useSurfaceShapeBatch() {
|
|||||||
|
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
roof.fire('polygonMoved')
|
roof.fire('polygonMoved')
|
||||||
if (roof.type === 'group') reGroupObject(obj)
|
|
||||||
drawDirectionArrow(roof)
|
drawDirectionArrow(roof)
|
||||||
initEvent()
|
initEvent()
|
||||||
})
|
})
|
||||||
@ -805,9 +759,74 @@ export function useSurfaceShapeBatch() {
|
|||||||
canvas?.remove(groupObj)
|
canvas?.remove(groupObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resizeSurfaceShapeBatch = (side, target, width, height) => {
|
||||||
|
const objectWidth = target.width
|
||||||
|
const objectHeight = target.height
|
||||||
|
const changeWidth = width / 10 / objectWidth
|
||||||
|
const changeHeight = height / 10 / objectHeight
|
||||||
|
let sideX = 'left'
|
||||||
|
let sideY = 'top'
|
||||||
|
|
||||||
|
//그룹 중심점 변경
|
||||||
|
if (side === 2) {
|
||||||
|
sideX = 'right'
|
||||||
|
sideY = 'top'
|
||||||
|
} else if (side === 3) {
|
||||||
|
sideX = 'left'
|
||||||
|
sideY = 'bottom'
|
||||||
|
} else if (side === 4) {
|
||||||
|
sideX = 'right'
|
||||||
|
sideY = 'bottom'
|
||||||
|
}
|
||||||
|
|
||||||
|
//변경 전 좌표
|
||||||
|
const newCoords = target.getPointByOrigin(sideX, sideY)
|
||||||
|
|
||||||
|
target.set({
|
||||||
|
originX: sideX,
|
||||||
|
originY: sideY,
|
||||||
|
left: newCoords.x,
|
||||||
|
top: newCoords.y,
|
||||||
|
})
|
||||||
|
|
||||||
|
target.scaleX = changeWidth
|
||||||
|
target.scaleY = changeHeight
|
||||||
|
|
||||||
|
const currentPoints = target.getCurrentPoints()
|
||||||
|
|
||||||
|
target.set({
|
||||||
|
scaleX: 1,
|
||||||
|
scaleY: 1,
|
||||||
|
width: parseInt((width / 10).toFixed(0)),
|
||||||
|
height: parseInt((height / 10).toFixed(0)),
|
||||||
|
})
|
||||||
|
//크기 변경후 좌표를 재 적용
|
||||||
|
const changedCoords = target.getPointByOrigin('center', 'center')
|
||||||
|
|
||||||
|
target.set({
|
||||||
|
originX: 'center',
|
||||||
|
originY: 'center',
|
||||||
|
left: changedCoords.x,
|
||||||
|
top: changedCoords.y,
|
||||||
|
})
|
||||||
|
|
||||||
|
//면형상 리사이즈시에만
|
||||||
|
target.fire('polygonMoved')
|
||||||
|
target.points = currentPoints
|
||||||
|
target.fire('modified')
|
||||||
|
|
||||||
|
setSurfaceShapePattern(target, roofDisplay.column)
|
||||||
|
if (target.direction) {
|
||||||
|
drawDirectionArrow(target)
|
||||||
|
}
|
||||||
|
target.setCoords()
|
||||||
|
canvas.renderAll()
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
applySurfaceShape,
|
applySurfaceShape,
|
||||||
deleteAllSurfacesAndObjects,
|
deleteAllSurfacesAndObjects,
|
||||||
moveSurfaceShapeBatch,
|
moveSurfaceShapeBatch,
|
||||||
|
resizeSurfaceShapeBatch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -187,6 +187,8 @@ export function usePlan() {
|
|||||||
userId: item.userId,
|
userId: item.userId,
|
||||||
canvasStatus: dbToCanvasFormat(item.canvasStatus),
|
canvasStatus: dbToCanvasFormat(item.canvasStatus),
|
||||||
isCurrent: false,
|
isCurrent: false,
|
||||||
|
bgImageName: item.bgImageName,
|
||||||
|
mapPositionAddress: item.mapPositionAddress,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -693,8 +693,7 @@ export const usePolygon = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const direction = polygon.direction ?? representLine.direction
|
||||||
const direction = newRoofs.length === 1 ? polygon.direction : representLine.direction
|
|
||||||
const polygonDirection = polygon.direction
|
const polygonDirection = polygon.direction
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
@ -723,7 +722,7 @@ export const usePolygon = () => {
|
|||||||
originY: 'center',
|
originY: 'center',
|
||||||
selectable: true,
|
selectable: true,
|
||||||
defense: defense,
|
defense: defense,
|
||||||
direction: newRoofs.length === 1 ? polygonDirection : defense,
|
direction: polygonDirection ?? defense,
|
||||||
pitch: pitch,
|
pitch: pitch,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -818,7 +818,7 @@
|
|||||||
"estimate.detail.estimateType": "注文分類",
|
"estimate.detail.estimateType": "注文分類",
|
||||||
"estimate.detail.roofCns": "屋根材・仕様施工",
|
"estimate.detail.roofCns": "屋根材・仕様施工",
|
||||||
"estimate.detail.remarks": "備考",
|
"estimate.detail.remarks": "備考",
|
||||||
"estimate.detail.nextSubmit": "後日資料提出",
|
"estimate.detail.fileFlg": "後日資料提出",
|
||||||
"estimate.detail.header.fileList1": "ファイル添付",
|
"estimate.detail.header.fileList1": "ファイル添付",
|
||||||
"estimate.detail.fileList.btn": "ファイル選択",
|
"estimate.detail.fileList.btn": "ファイル選択",
|
||||||
"estimate.detail.header.fileList2": "添付ファイル一覧",
|
"estimate.detail.header.fileList2": "添付ファイル一覧",
|
||||||
@ -835,11 +835,60 @@
|
|||||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
|
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
|
||||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
|
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
|
||||||
"estimate.detail.header.showPrice": "価格表示",
|
"estimate.detail.header.showPrice": "価格表示",
|
||||||
"estimate.detail.showPrice.btn1": "Pricing",
|
"estimate.detail.header.unitPrice": "定価",
|
||||||
|
"estimate.detail.showPrice.pricingBtn": "Pricing",
|
||||||
"estimate.detail.showPrice.description1": "製品価格 OPEN",
|
"estimate.detail.showPrice.description1": "製品価格 OPEN",
|
||||||
"estimate.detail.showPrice.description2": "追加, 変更資材",
|
"estimate.detail.showPrice.description2": "追加, 変更資材",
|
||||||
"estimate.detail.showPrice.description3": "添付必須",
|
"estimate.detail.showPrice.description3": "添付必須",
|
||||||
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
|
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
|
||||||
"estimate.detail.showPrice.btn2": "製品を追加",
|
"estimate.detail.showPrice.btn2": "製品を追加",
|
||||||
"estimate.detail.showPrice.btn3": "製品削除"
|
"estimate.detail.showPrice.btn3": "製品削除",
|
||||||
|
"estimate.detail.itemTableHeader.col1": "アイテム",
|
||||||
|
"estimate.detail.itemTableHeader.col2": "品番",
|
||||||
|
"estimate.detail.itemTableHeader.col3": "型板",
|
||||||
|
"estimate.detail.itemTableHeader.col4": "数量",
|
||||||
|
"estimate.detail.itemTableHeader.col5": "単位",
|
||||||
|
"estimate.detail.itemTableHeader.col6": "単価",
|
||||||
|
"estimate.detail.itemTableHeader.col7": "金額 (税別別)",
|
||||||
|
"estimate.detail.docPopup.title": "ドキュメントダウンロードオプションの設定",
|
||||||
|
"estimate.detail.docPopup.explane": "ダウンロードする文書のオプションを選択したら、 [文書のダウンロード]ボタンをクリックします.",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg": "ダウンロードファイル",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0": "見積もり Excel",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1": "定価用 Excel",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2": "見積もり PDF",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3": "定価用 PDF",
|
||||||
|
"estimate.detail.docPopup.schDisplayFlg": "見積提出先表示名",
|
||||||
|
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0": "販売店名",
|
||||||
|
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1": "案件名",
|
||||||
|
"estimate.detail.docPopup.schWeightFlg": "架台重量表を含む",
|
||||||
|
"estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "含む",
|
||||||
|
"estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "含まない",
|
||||||
|
"estimate.detail.docPopup.schDrawingFlg": "図面/シミュレーションファイルを含む",
|
||||||
|
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "含む",
|
||||||
|
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "含まない",
|
||||||
|
"estimate.detail.docPopup.close": "閉じる",
|
||||||
|
"estimate.detail.docPopup.docDownload": "文書のダウンロード",
|
||||||
|
"estimate.detail.save.alertMsg": "保存されている見積書で製品を変更した場合、図面や回路には反映されません.",
|
||||||
|
"estimate.detail.save.requiredMsg": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください.",
|
||||||
|
"estimate.detail.reset.confirmMsg": "保存した見積書情報が初期化され、図面情報が反映されます。本当に初期化しますか?",
|
||||||
|
"simulator.title.sub1": "物件番号",
|
||||||
|
"simulator.title.sub2": "作成日",
|
||||||
|
"simulator.title.sub3": "システム容量",
|
||||||
|
"simulator.title.sub4": "年間予測発電量",
|
||||||
|
"simulator.title.sub5": "都道府県",
|
||||||
|
"simulator.title.sub6": "日射量観測地点",
|
||||||
|
"simulator.title.sub7": "積雪条件",
|
||||||
|
"simulator.title.sub8": "風速条件",
|
||||||
|
"simulator.title.sub9": "以下",
|
||||||
|
"simulator.table.sub1": "屋根面",
|
||||||
|
"simulator.table.sub2": "傾斜角",
|
||||||
|
"simulator.table.sub3": "方位角(度)",
|
||||||
|
"simulator.table.sub4": "太陽電池モジュール",
|
||||||
|
"simulator.table.sub5": "枚数",
|
||||||
|
"simulator.table.sub6": "合計",
|
||||||
|
"simulator.table.sub7": "パワーコンディショナー",
|
||||||
|
"simulator.table.sub8": "台",
|
||||||
|
"simulator.table.sub9": "予測発電量 (kWh)",
|
||||||
|
"simulator.notice.sub1": "Hanwha Japan 年間発電量",
|
||||||
|
"simulator.notice.sub2": "シミュレーション案内事項"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -824,7 +824,7 @@
|
|||||||
"estimate.detail.estimateType": "주문분류",
|
"estimate.detail.estimateType": "주문분류",
|
||||||
"estimate.detail.roofCns": "지붕재・사양시공",
|
"estimate.detail.roofCns": "지붕재・사양시공",
|
||||||
"estimate.detail.remarks": "비고",
|
"estimate.detail.remarks": "비고",
|
||||||
"estimate.detail.nextSubmit": "후일자료제출",
|
"estimate.detail.fileFlg": "후일자료제출",
|
||||||
"estimate.detail.header.fileList1": "파일첨부",
|
"estimate.detail.header.fileList1": "파일첨부",
|
||||||
"estimate.detail.fileList.btn": "파일선택",
|
"estimate.detail.fileList.btn": "파일선택",
|
||||||
"estimate.detail.header.fileList2": "첨부파일 목록",
|
"estimate.detail.header.fileList2": "첨부파일 목록",
|
||||||
@ -841,11 +841,60 @@
|
|||||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100",
|
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100",
|
||||||
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
|
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
|
||||||
"estimate.detail.header.showPrice": "가격표시",
|
"estimate.detail.header.showPrice": "가격표시",
|
||||||
"estimate.detail.showPrice.btn1": "Pricing",
|
"estimate.detail.header.unitPrice": "정가",
|
||||||
|
"estimate.detail.showPrice.pricingBtn": "Pricing",
|
||||||
"estimate.detail.showPrice.description1": "제품 가격 OPEN",
|
"estimate.detail.showPrice.description1": "제품 가격 OPEN",
|
||||||
"estimate.detail.showPrice.description2": "추가, 변경 자재",
|
"estimate.detail.showPrice.description2": "추가, 변경 자재",
|
||||||
"estimate.detail.showPrice.description3": "첨부필수",
|
"estimate.detail.showPrice.description3": "첨부필수",
|
||||||
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
|
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
|
||||||
"estimate.detail.showPrice.btn2": "제품추가",
|
"estimate.detail.showPrice.btn2": "제품추가",
|
||||||
"estimate.detail.showPrice.btn3": "제품삭제"
|
"estimate.detail.showPrice.btn3": "제품삭제",
|
||||||
|
"estimate.detail.itemTableHeader.col1": "Item",
|
||||||
|
"estimate.detail.itemTableHeader.col2": "품번",
|
||||||
|
"estimate.detail.itemTableHeader.col3": "형명",
|
||||||
|
"estimate.detail.itemTableHeader.col4": "수량",
|
||||||
|
"estimate.detail.itemTableHeader.col5": "단위",
|
||||||
|
"estimate.detail.itemTableHeader.col6": "단가",
|
||||||
|
"estimate.detail.itemTableHeader.col7": "금액(부가세별도)",
|
||||||
|
"estimate.detail.docPopup.title": "문서다운로드 옵션설정",
|
||||||
|
"estimate.detail.docPopup.explane": "다운로드할 문서 옵션을 선택한 후 문서 다운로드 버튼을 클릭합니다.",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg": "다운로드 파일",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0": "견적가 Excel",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1": "정가용 Excel",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2": "견적가 PDF",
|
||||||
|
"estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3": "정가용 PDF",
|
||||||
|
"estimate.detail.docPopup.schDisplayFlg": "견적제출서 표시명",
|
||||||
|
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0": "판매점명",
|
||||||
|
"estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1": "안건명",
|
||||||
|
"estimate.detail.docPopup.schWeightFlg": "가대 중량표 포함",
|
||||||
|
"estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "포함",
|
||||||
|
"estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "미포함",
|
||||||
|
"estimate.detail.docPopup.schDrawingFlg": "도면/시뮬레이션 파일 포함",
|
||||||
|
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "포함",
|
||||||
|
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "미포함",
|
||||||
|
"estimate.detail.docPopup.close": "닫기",
|
||||||
|
"estimate.detail.docPopup.docDownload": "문서 다운로드",
|
||||||
|
"estimate.detail.save.alertMsg": "저장되었습니다. 견적서에서 제품을 변경할 경우, 도면 및 회로에 반영되지 않습니다.",
|
||||||
|
"estimate.detail.save.requiredMsg": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.",
|
||||||
|
"estimate.detail.reset.confirmMsg": "저장된 견적서 정보가 초기화되고, 도면정보가 반영됩니다. 정말로 초기화 하시겠습니까?",
|
||||||
|
"simulator.title.sub1": "물건번호",
|
||||||
|
"simulator.title.sub2": "작성일",
|
||||||
|
"simulator.title.sub3": "시스템 용량",
|
||||||
|
"simulator.title.sub4": "연간예측발전량",
|
||||||
|
"simulator.title.sub5": "도도부현",
|
||||||
|
"simulator.title.sub6": "일사량 관측지점",
|
||||||
|
"simulator.title.sub7": "적설조건",
|
||||||
|
"simulator.title.sub8": "풍속조건",
|
||||||
|
"simulator.title.sub9": "이하",
|
||||||
|
"simulator.table.sub1": "지붕면",
|
||||||
|
"simulator.table.sub2": "경사각",
|
||||||
|
"simulator.table.sub3": "방위각(도)",
|
||||||
|
"simulator.table.sub4": "태양전지모듈",
|
||||||
|
"simulator.table.sub5": "매수",
|
||||||
|
"simulator.table.sub6": "합계",
|
||||||
|
"simulator.table.sub7": "파워 컨디셔너",
|
||||||
|
"simulator.table.sub8": "대",
|
||||||
|
"simulator.table.sub9": "예측발전량 (kWh)",
|
||||||
|
"simulator.notice.sub1": "Hanwha Japan 연간 발전량",
|
||||||
|
"simulator.notice.sub2": "시뮬레이션 안내사항"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,6 +185,7 @@ export const corridorDimensionSelector = selector({
|
|||||||
const settingModalFirstOptions = get(settingModalFirstOptionsState)
|
const settingModalFirstOptions = get(settingModalFirstOptionsState)
|
||||||
return settingModalFirstOptions.dimensionDisplay.find((option) => option.selected)
|
return settingModalFirstOptions.dimensionDisplay.find((option) => option.selected)
|
||||||
},
|
},
|
||||||
|
dangerouslyAllowMutability: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 디스플레이 설정 - 화면 표시
|
// 디스플레이 설정 - 화면 표시
|
||||||
|
|||||||
17
yarn.lock
17
yarn.lock
@ -527,6 +527,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz"
|
resolved "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz"
|
||||||
integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA==
|
integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA==
|
||||||
|
|
||||||
|
"@kurkle/color@^0.3.0":
|
||||||
|
version "0.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
|
||||||
|
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
|
||||||
|
|
||||||
"@mapbox/node-pre-gyp@^1.0.0":
|
"@mapbox/node-pre-gyp@^1.0.0":
|
||||||
version "1.0.11"
|
version "1.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
|
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
|
||||||
@ -4337,6 +4342,13 @@ chalk@^2.4.2:
|
|||||||
escape-string-regexp "^1.0.5"
|
escape-string-regexp "^1.0.5"
|
||||||
supports-color "^5.3.0"
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
|
chart.js@^4.4.6:
|
||||||
|
version "4.4.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.6.tgz#da39b84ca752298270d4c0519675c7659936abec"
|
||||||
|
integrity sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==
|
||||||
|
dependencies:
|
||||||
|
"@kurkle/color" "^0.3.0"
|
||||||
|
|
||||||
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
|
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
|
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
|
||||||
@ -5832,6 +5844,11 @@ rbush@^3.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
quickselect "^2.0.0"
|
quickselect "^2.0.0"
|
||||||
|
|
||||||
|
react-chartjs-2@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d"
|
||||||
|
integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==
|
||||||
|
|
||||||
react-color-palette@^7.2.2:
|
react-color-palette@^7.2.2:
|
||||||
version "7.2.2"
|
version "7.2.2"
|
||||||
resolved "https://registry.npmjs.org/react-color-palette/-/react-color-palette-7.2.2.tgz"
|
resolved "https://registry.npmjs.org/react-color-palette/-/react-color-palette-7.2.2.tgz"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user