This commit is contained in:
minsik 2024-11-08 15:02:45 +09:00
commit 57fa5f8fef
25 changed files with 1407 additions and 157 deletions

View File

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

View File

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

View File

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

View File

@ -431,7 +431,16 @@ export default function Roof2(props) {
{ x: 450, y: 850 },
]
const polygon = new QPolygon(type2, {
const test1 = [
{ x: 381, y: 178 },
{ x: 381, y: 659.3 },
{ x: 773.3, y: 659.3 },
{ x: 773.3, y: 497.9 },
{ x: 1457, y: 497.9 },
{ x: 1457, y: 178 },
]
const polygon = new QPolygon(test1, {
fill: 'transparent',
stroke: 'green',
strokeWidth: 1,

View File

@ -9,12 +9,13 @@ import SingleDatePicker from '../common/datepicker/SingleDatePicker'
import EstimateFileUploader from './EstimateFileUploader'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { isNotEmptyArray, isObjectNotEmpty } from '@/util/common-utils'
import { isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import dayjs from 'dayjs'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import Select from 'react-select'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { SessionContext } from '@/app/SessionProvider'
import Select, { components } from 'react-select'
// import EstimateItemTable from './EstimateItemTable'
export default function Estimate({ params }) {
const { session } = useContext(SessionContext)
@ -33,6 +34,8 @@ export default function Estimate({ params }) {
const { findCommonCode } = useCommonCode()
const [honorificCodeList, setHonorificCodeList] = useState([]) //
const [storePriceList, setStorePriceList] = useState([]) // option
const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = {
startDate,
@ -44,7 +47,7 @@ export default function Estimate({ params }) {
//
const { state, setState } = useEstimateController(params.pid)
// LIST
const [itemList, setItemList] = useState([])
// List
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 (
<div className="sub-content estimate">
<div className="sub-content-inner">
@ -304,6 +347,7 @@ export default function Estimate({ params }) {
value={'YJSS'}
checked={state?.estimateType === 'YJSS' ? true : false}
onChange={(e) => {
//
setState({ estimateType: e.target.value })
}}
/>
@ -380,9 +424,18 @@ export default function Estimate({ params }) {
<div className="title-wrap">
<h3>{getMessage('estimate.detail.header.fileList1')}</h3>
<div className="d-check-box light mr5">
<input type="checkbox" id="next" />
<input
type="checkbox"
id="next"
checked={state?.fileFlg === '0' ? false : true}
onChange={(e) => {
setState({
fileFlg: e.target.checked ? '1' : '0',
})
}}
/>
<label htmlFor="next" style={{ color: '#101010' }}>
{getMessage('estimate.detail.nextSubmit')}
{getMessage('estimate.detail.fileFlg')}
</label>
</div>
</div>
@ -416,7 +469,7 @@ export default function Estimate({ params }) {
<td>
<div className="drag-file-box">
<ul className="file-list">
{isNotEmptyArray(originFiles) &&
{originFiles.length > 0 &&
originFiles.map((originFile) => {
return (
<li className="file-item">
@ -486,13 +539,13 @@ export default function Estimate({ params }) {
return (
<dl>
<dt>{row.codeNm}</dt>
<dd>{row.remarks}</dd>
<dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd>
</dl>
)
}
})}
</div>
{/* 견적특이사항 선택한 내용?영역끝 */}
{/* 견적특이사항 선택한 내용 영역끝 */}
</div>
</div>
@ -565,12 +618,30 @@ export default function Estimate({ params }) {
<div className="product-price-wrap">
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
<div className="select-wrap">
<select className="select-light" name="" id="">
<option value="">111</option>
<option value="">222</option>
</select>
{session?.storeLvl === '1' ? (
<select
className="select-light"
onChange={(e) => {
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>
<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 className="product-edit-wrap">
<ul className="product-edit-explane">
@ -605,7 +676,83 @@ export default function Estimate({ params }) {
</div>
{/* 가격표시영역끝 */}
{/* html테이블시작 */}
<div className="q-grid no-cols"></div>
<div className="esimate-table">
<table>
<colgroup>
<col width={50} />
<col width={100} />
<col />
<col width={200} />
<col width={100} />
<col width={100} />
<col width={200} />
<col width={240} />
</colgroup>
<thead>
<tr>
<th>
<div className="d-check-box pop no-text" style={{ display: 'none' }}>
<input type="checkbox" id="ch97" />
<label htmlFor="ch97"></label>
</div>
</th>
<th>{getMessage('estimate.detail.itemTableHeader.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테이블끝 */}
</div>
</div>

View File

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

View File

@ -0,0 +1,255 @@
'use client'
import { useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
import { useRecoilValue } from 'recoil'
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) {
// console.log('::::::::::::', planNo)
const { getMessage } = useMessage()
const { promiseGet } = useAxios()
// EXCEL
const [schUnitPriceFlg, setSchUnitPriceFlg] = useState('0')
//
const [schDisplayFlg, setSchSchDisplayFlg] = useState('0')
//
const [schWeightFlg, setSchWeightFlg] = useState('0')
///
const [schDrawingFlg, setSchDrawingFlg] = useState('0')
// recoil
const objectRecoil = useRecoilValue(floorPlanObjectState)
//
const handleFileDown = async () => {
// console.log(':::', objectRecoil.floorPlanObjectNo)
// console.log('planNo::', planNo)
//
//0 : Excel 1 : Excel 2: PDF 3 :PDF
// console.log(schUnitPriceFlg)
// console.log(schDisplayFlg)
// console.log(schWeightFlg)
// console.log(schDrawingFlg)
const url = '/api/estimate/excel-download'
const params = {}
const options = { responseType: 'blob' }
}
return (
<div className="modal-popup">
<div className="modal-dialog middle">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('estimate.detail.docPopup.title')}</h1>
<button
type="button"
className="modal-close"
onClick={() => {
setEstimatePopupOpen(false)
}}
>
{getMessage('estimate.detail.docPopup.close')}
</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="explane">{getMessage('estimate.detail.docPopup.explane')}</div>
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '260px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>
{getMessage('estimate.detail.docPopup.schUnitPriceFlg')}
<span className="red">*</span>
</th>
<td>
<div className="form-flex-wrap">
<div className="d-check-radio light mr10">
<input
type="radio"
id="schUnitPriceFlg0"
name="schUnitPriceFlg"
value={'0'}
checked={schUnitPriceFlg === '0'}
onChange={(e) => {
setSchUnitPriceFlg(e.target.value)
}}
/>
<label htmlFor="schUnitPriceFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0')}</label>
</div>
<div className="d-check-radio light mr10">
<input
type="radio"
id="schUnitPriceFlg1"
name="schUnitPriceFlg"
value={'1'}
checked={schUnitPriceFlg === '1'}
onChange={(e) => {
setSchUnitPriceFlg(e.target.value)
}}
/>
<label htmlFor="schUnitPriceFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1')}</label>
</div>
<div className="d-check-radio light mr10">
<input
type="radio"
id="schUnitPricePdfFlg0"
name="schUnitPriceFlg"
value={'2'}
checked={schUnitPriceFlg === '2'}
onChange={(e) => {
setSchUnitPriceFlg(e.target.value)
}}
/>
<label htmlFor="schUnitPricePdfFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2')}</label>
</div>
<div className="d-check-radio light ">
<input
type="radio"
id="schUnitPricePdfFlg1"
name="schUnitPriceFlg"
value={'3'}
checked={schUnitPriceFlg === '3'}
onChange={(e) => {
setSchUnitPriceFlg(e.target.value)
}}
/>
<label htmlFor="schUnitPricePdfFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3')}</label>
</div>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('estimate.detail.docPopup.schDisplayFlg')} <span className="red">*</span>
</th>
<td>
<div className="form-flex-wrap">
<div className="d-check-radio light mr10">
<input
type="radio"
name="schDisplayFlg"
id="schDisplayFlg0"
value={'0'}
checked={schDisplayFlg === '0'}
onChange={(e) => {
setSchSchDisplayFlg(e.target.value)
}}
/>
<label htmlFor="schDisplayFlg0">{getMessage('estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="schDisplayFlg"
id="schDisplayFlg1"
value={'1'}
checked={schDisplayFlg === '1'}
onChange={(e) => {
setSchSchDisplayFlg(e.target.value)
}}
/>
<label htmlFor="schDisplayFlg1">{getMessage('estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1')}</label>
</div>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('estimate.detail.docPopup.schWeightFlg')} <span className="red">*</span>
</th>
<td>
<div className="form-flex-wrap">
<div className="d-check-radio light mr10">
<input
type="radio"
name="schWeightFlg"
id="schWeightFlg0"
value={'0'}
checked={schWeightFlg === '0'}
onChange={(e) => {
setSchWeightFlg(e.target.value)
}}
/>
<label htmlFor="schWeightFlg0">{getMessage('estimate.detail.docPopup.schWeightFlg.schWeightFlg0')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="schWeightFlg"
id="schWeightFlg1"
value={'1'}
checked={schWeightFlg === '1'}
onChange={(e) => {
setSchWeightFlg(e.target.value)
}}
/>
<label htmlFor="schWeightFlg1">{getMessage('estimate.detail.docPopup.schWeightFlg.schWeightFlg1')}</label>
</div>
</div>
</td>
</tr>
<tr>
<th>{getMessage('estimate.detail.docPopup.schDrawingFlg')}</th>
<td>
<div className="form-flex-wrap">
<div className="d-check-radio light mr10">
<input
type="radio"
name="schDrawingFlg"
id="schDrawingFlg0"
value={'0'}
checked={schDrawingFlg === '0'}
onChange={(e) => {
setSchDrawingFlg(e.target.value)
}}
/>
<label htmlFor="schDrawingFlg0">{getMessage('estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="schDrawingFlg"
id="schDrawingFlg01"
value={'1'}
checked={schDrawingFlg === '1'}
onChange={(e) => {
setSchDrawingFlg(e.target.value)
}}
/>
<label htmlFor="schDrawingFlg01">{getMessage('estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1')}</label>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="footer-btn-wrap">
<button
type="button"
className="btn-origin grey mr5"
onClick={() => {
setEstimatePopupOpen(false)
}}
>
{getMessage('estimate.detail.docPopup.close')}
</button>
<button type="button" className="btn-origin navy" onClick={() => handleFileDown()}>
{getMessage('estimate.detail.docPopup.docDownload')}
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -790,7 +790,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
setViewLengthText(isView) {
this.canvas
?.getObjects()
.filter((obj) => obj.name === 'lengthText' && obj.parent === this)
.filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
.forEach((text) => {
text.set({ visible: isView })
})
@ -803,9 +803,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.scaleY = scale
this.addLengthText()
},
divideLine() {
// splitPolygonWithLines(this)
},
calcOriginCoords() {
const points = this.points
const minX = Math.min(...points.map((p) => p.x))

View File

@ -33,6 +33,8 @@ import useMenu from '@/hooks/common/useMenu'
import { MENU } from '@/common/common'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { estimateState } from '@/store/floorPlanObjectAtom'
import DocDownOptionPop from '../estimate/popup/DocDownOptionPop'
export default function CanvasMenu(props) {
const { menuNumber, setMenuNumber } = props
@ -53,7 +55,10 @@ export default function CanvasMenu(props) {
const canvas = useRecoilValue(canvasState)
const { handleZoomClear, handleZoom } = useCanvasEvent()
const { handleMenu } = useMenu()
const { handleEstimateSubmit } = useEstimateController()
const estimateRecoilState = useRecoilValue(estimateState)
const [estimatePopupOpen, setEstimatePopupOpen] = useState(false)
const { getMessage } = useMessage()
const { currentCanvasPlan, saveCanvas } = usePlan()
@ -81,6 +86,9 @@ export default function CanvasMenu(props) {
case 4:
setType('module')
break
case 6:
router.push(`/floor-plan/simulator/${menu.index}`)
break
}
if (pathname !== '/floor-plan') router.push('/floor-plan')
@ -135,6 +143,38 @@ export default function CanvasMenu(props) {
addPopup(id, 1, <SettingModal01 id={id} />, true)
}
//
const handleEstimateReset = () => {
// console.log('estimateRecoilState::', estimateRecoilState)
//objectNo, planNo
swalFire({
// , . ?
//
text: getMessage('estimate.detail.reset.confirmMsg'),
type: 'confirm',
confirmFn: () => {
console.log('내용초기화 및 변경일시 갱신')
},
denyFn: () => {
console.log('초기화하지 않음. 변경일시 갱신안함')
},
})
}
/**
* 견적서 복사버튼
* (견적서 번호(estimateRecoilState.docNo) 생성된 이후 버튼 활성화 )
* T01관리자 계정 1차판매점에게만 제공
*/
const handleEstimateCopy = () => {
// console.log('estimateRecoilState::', estimateRecoilState)
//objectNo, planNo
console.log('복사')
console.log('물건정보+도면+견적서를 모두 복사')
console.log('견적서 가격은 정가를 표시')
}
useEffect(() => {
if (globalLocale === 'ko') {
setAppMessageState(KO)
@ -225,7 +265,7 @@ export default function CanvasMenu(props) {
{menuNumber === 5 && (
<>
<div className="ico-btn-from">
<button className="btn-frame gray ico-flx act">
<button className="btn-frame gray ico-flx" onClick={() => setEstimatePopupOpen(true)}>
<span className="ico ico01"></span>
<span>{getMessage('plan.menu.estimate.docDown')}</span>
</button>
@ -233,11 +273,24 @@ export default function CanvasMenu(props) {
<span className="ico ico02"></span>
<span>{getMessage('plan.menu.estimate.save')}</span>
</button>
<button className="btn-frame gray ico-flx">
{/* {estimateRecoilState?.docNo != null && ( */}
<button
className="btn-frame gray ico-flx"
onClick={() => {
handleEstimateReset()
}}
>
<span className="ico ico03"></span>
<span>{getMessage('plan.menu.estimate.reset')}</span>
</button>
<button className="btn-frame gray ico-flx">
{/* )} */}
<button
className="btn-frame gray ico-flx"
onClick={() => {
handleEstimateCopy()
}}
>
<span className="ico ico04"></span>
<span>{getMessage('plan.menu.estimate.copy')}</span>
</button>
@ -263,6 +316,8 @@ export default function CanvasMenu(props) {
<div className={`canvas-depth2-wrap ${menuNumber === 2 || menuNumber === 3 || menuNumber === 4 ? 'active' : ''}`}>
{(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && <MenuDepth01 />}
</div>
{/* 견적서(menuNumber=== 5) 상세화면인경우 문서다운로드 팝업 */}
{estimatePopupOpen && <DocDownOptionPop planNo={estimateRecoilState?.planNo} setEstimatePopupOpen={setEstimatePopupOpen} />}
</div>
)
}

View File

@ -1,14 +1,20 @@
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { useEffect, useState } from 'react'
import { useRecoilState } from 'recoil'
import { Fragment, useEffect, useState } from 'react'
import { canvasSettingState } from '@/store/canvasAtom'
import { basicSettingState } from '@/store/settingAtom'
import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup'
import { basicSettingState } from '@/store/settingAtom'
import useRefFiles from '@/hooks/common/useRefFiles'
import { usePlan } from '@/hooks/usePlan'
import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide'
import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { SessionContext } from '@/app/SessionProvider'
export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) {
const [objectNo, setObjectNo] = useState('test123241008001') //
@ -18,7 +24,20 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
const { closePopup } = usePopup()
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
const [image, setImage] = useState(null)
const {
refImage,
queryRef,
setRefImage,
handleRefFile,
refFileMethod,
setRefFileMethod,
handleRefFileMethod,
mapPositionAddress,
setMapPositionAddress,
handleFileDelete,
handleMapImageDown,
} = useRefFiles()
const { currentCanvasPlan } = usePlan()
const { getMessage } = useMessage()
const { get, post } = useAxios()
@ -487,19 +506,85 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
<tr>
<th>{getMessage('common.input.file')}</th>
<td>
<div className="flex-box">
<div className="pop-form-radio mb10">
<div className="d-check-radio pop">
<input
type="radio"
name="radio03"
id="ra06"
value={'1'}
onChange={(e) => handleRefFileMethod(e)}
checked={refFileMethod === '1'}
/>
<label htmlFor="ra06">ファイルを読み込む</label>
</div>
<div className="d-check-radio pop">
<input
type="radio"
name="radio03"
id="ra07"
value={'2'}
onChange={(e) => handleRefFileMethod(e)}
checked={refFileMethod === '2'}
/>
<label htmlFor="ra07">アドレスを読み込む</label>
</div>
</div>
{/* 파일 불러오기 */}
{refFileMethod === '1' && (
<div className="flex-box">
<div className="img-edit-wrap">
<label className="img-edit-btn" htmlFor="img_file">
<span className="img-edit"></span>
{getMessage('common.input.file.load')}
</label>
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
</div>
<div className="img-name-wrap">
{currentCanvasPlan?.bgImageName === null ? (
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
) : (
<input type="text" className="input-origin al-l" value={currentCanvasPlan?.bgImageName} readOnly />
)}
{(refImage || currentCanvasPlan?.bgImageName) && <button className="img-check" onClick={handleFileDelete}></button>}
</div>
</div>
)}
{/* 주소 불러오기 */}
{refFileMethod === '2' && (
<div className="flex-box for-address">
<input
type="text"
className="input-origin al-l mr10"
placeholder={'住所入力'}
ref={queryRef}
value={mapPositionAddress}
onChange={(e) => setMapPositionAddress(e.target.value)}
/>
<div className="img-edit-wrap mr5">
<button className="img-edit-btn" onClick={handleMapImageDown}>
完了
</button>
</div>
{mapPositionAddress && <span className="check-address fail"></span>}
{/* <span className="check-address success"></span> */}
</div>
)}
{/* <div className="flex-box">
<div className="img-edit-wrap">
<label className="img-edit-btn" htmlFor="img_file">
<span className="img-edit"></span>
{getMessage('common.input.file.load')}
</label>
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => setImage(e.target.files[0])} />
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
</div>
<div className="img-name-wrap">
<input type="text" className="input-origin al-l" defaultValue={''} value={image ? image.name : ''} readOnly />
{image && <button className="img-check" onClick={() => setImage(null)}></button>}
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
{refImage && <button className="img-check" onClick={() => setRefImage(null)}></button>}
</div>
</div>
</div> */}
</td>
</tr>
</tbody>

View File

@ -190,9 +190,11 @@ export default function Stuff() {
})
}
if (stuffSearch.schSelSaleStoreId !== '') {
fetchData()
}
//if (session.storeId === 'T01') {
fetchData()
//} else if (stuffSearch.schSelSaleStoreId !== '') {
//fetchData()
//}
} else if (stuffSearchParams?.code === 'M') {
const params = {
saleStoreId: session?.storeId,

View File

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

View File

@ -184,34 +184,36 @@ export default function StuffSearchCondition() {
setSchSelSaleStoreList(allList)
setFavoriteStoreList(favList)
setShowSaleStoreList(favList)
setSchSelSaleStoreId(session?.storeId)
// setSchSelSaleStoreId(session?.storeId)
setStuffSearch({
...stuffSearch,
code: 'S',
schSelSaleStoreId: session?.storeId,
// schSelSaleStoreId: session?.storeId,
})
//T01 2 1 storeId
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}`
//
// url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}`
get({ url: url }).then((res) => {
if (!isEmptyArray(res)) {
res.map((row) => {
row.value = row.saleStoreId
row.label = row.saleStoreName
})
// get({ url: url }).then((res) => {
// if (!isEmptyArray(res)) {
// res.map((row) => {
// row.value = row.saleStoreId
// row.label = row.saleStoreName
// })
otherList = res
setOtherSaleStoreList(otherList)
} else {
setOtherSaleStoreList([])
}
})
// otherList = res
// setOtherSaleStoreList(otherList)
// } else {
// setOtherSaleStoreList([])
// }
// })
} else {
if (session?.storeLvl === '1') {
allList = res
favList = res.filter((row) => row.priority !== 'B')
otherList = res.filter((row) => row.firstAgentYn === 'N')
setSchSelSaleStoreList(allList)
setFavoriteStoreList(allList)
setShowSaleStoreList(allList)
@ -584,7 +586,7 @@ export default function StuffSearchCondition() {
onChange={onSelectionChange2}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isDisabled={otherSaleStoreList.length > 1 ? false : true}
isDisabled={otherSaleStoreList.length > 0 ? false : true}
isClearable={true}
value={otherSaleStoreList.filter(function (option) {
return option.saleStoreId === otherSaleStoreId

View File

@ -0,0 +1,365 @@
'use client'
import 'chart.js/auto'
import { Bar } from 'react-chartjs-2'
import dayjs from 'dayjs'
import { useEffect, useState, useRef } from 'react'
import { useRecoilValue } from 'recoil'
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage'
import { usePlan } from '@/hooks/usePlan'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { convertNumberToPriceDecimal } from '@/util/common-utils'
export default function Simulator() {
const { plans } = usePlan()
const plan = plans.find((plan) => plan.isCurrent === true)
const chartRef = useRef(null)
// recoil
const objectRecoil = useRecoilValue(floorPlanObjectState)
const [objectNo, setObjectNo] = useState('')
useEffect(() => {
setObjectNo(objectRecoil.floorPlanObjectNo)
}, [objectRecoil])
//
const { setMenuNumber } = useCanvasMenu()
useEffect(() => {
setMenuNumber(6)
}, [])
const { get } = useAxios()
const { getMessage } = useMessage()
//
const [chartData, setChartData] = useState([])
const data = {
labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
datasets: [
{
label: 'kWh',
data: chartData.slice(0, 12),
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(0, 99, 132, 0.2)',
'rgba(0, 162, 235, 0.2)',
'rgba(0, 206, 86, 0.2)',
'rgba(0, 192, 192, 0.2)',
'rgba(0, 102, 255, 0.2)',
'rgba(0, 159, 64, 0.2)',
],
borderColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(0, 99, 132, 0.2)',
'rgba(0, 162, 235, 0.2)',
'rgba(0, 206, 86, 0.2)',
'rgba(0, 192, 192, 0.2)',
'rgba(0, 102, 255, 0.2)',
'rgba(0, 159, 64, 0.2)',
],
borderWidth: 1,
},
],
}
const options = {
plugins: {
legend: {
position: 'top',
},
},
scales: {
x: {
grid: {
display: false,
},
},
y: {
beginAtZero: true,
grid: {
display: true,
},
},
},
}
useEffect(() => {
if (objectNo) {
fetchObjectDetail(objectNo)
}
fetchSimulatorNotice()
}, [objectNo, plan])
//
const [objectDetail, setObjectDetail] = useState({})
//
const [moduleInfoList, setModuleInfoList] = useState([])
//
const [pcsInfoList, setPcsInfoList] = useState([])
const fetchObjectDetail = async (objectNo) => {
const apiUrl = `/api/pwrGnrSimulation/calculations?objectNo=${objectNo}&planNo=${plan?.id}`
const resultData = await get({ url: apiUrl })
if (resultData) {
setObjectDetail(resultData)
if (resultData.frcPwrGnrList) {
setChartData(resultData.frcPwrGnrList)
}
if (resultData.pcsList) {
setPcsInfoList(resultData.pcsList)
}
if (resultData.roofModuleList) {
setModuleInfoList(resultData.roofModuleList)
}
}
}
//
const [content, setContent] = useState('')
const fetchSimulatorNotice = async () => {
get({ url: '/api/pwrGnrSimulation/guideInfo' }).then((res) => {
if (res.data) {
setContent(res.data.replaceAll('\n', '<br/>'))
} else {
setContent(getMessage('common.message.no.data'))
}
})
}
return (
<div className="sub-content estimate">
<div className="sub-content-inner">
<div className="sub-content-box">
<div className="sub-table-box">
<div className="estimate-list-wrap">
{/* 물건번호 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub1')}</div>
<div className="estimate-name">
{objectDetail.objectNo} (Plan No: {objectDetail.planNo})
</div>
</div>
{/* 작성일 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub2')}</div>
<div className="estimate-name">{`${dayjs(objectDetail.drawingEstimateCreateDate).format('YYYY.MM.DD')}`}</div>
</div>
{/* 시스템용량 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub3')}</div>
<div className="estimate-name">{convertNumberToPriceDecimal(objectDetail.capacity)}kW</div>
</div>
{/* 연간예측발전량 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub4')}</div>
<div className="estimate-name">{convertNumberToPriceDecimal(objectDetail.anlFrcsGnrt)}</div>
</div>
</div>
<div className="estimate-list-wrap">
{/* 도도부현 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub5')}</div>
<div className="estimate-name">{objectDetail.prefName}</div>
</div>
{/* 일사량 관측지점 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub6')}</div>
<div className="estimate-name">{objectDetail.areaName}</div>
</div>
{/* 적설조건 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub7')}</div>
<div className="estimate-name">{objectDetail.snowfall}</div>
</div>
{/* 풍속조건 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub8')}</div>
<div className="estimate-name">{objectDetail.standardWindSpeedId}</div>
</div>
</div>
</div>
</div>
<div className="sub-content-box">
<div className="chart-wrap">
<div className="chart-inner">
<div className="sub-table-box">
<div className="chart-box">
<Bar ref={chartRef} data={data} options={options} />
</div>
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3>{getMessage('simulator.table.sub9')} </h3>
</div>
</div>
{/* 예측발전량 */}
<div className="chart-month-table">
<table>
<thead>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
<th>7</th>
<th>8</th>
<th>9</th>
<th>10</th>
<th>11</th>
<th>12</th>
<th>{getMessage('simulator.table.sub6')}</th>
</tr>
</thead>
<tbody>
{chartData.length > 0 ? (
<tr>
{chartData.map((data) => (
<td key={data}>{convertNumberToPriceDecimal(data)}</td>
))}
</tr>
) : (
<tr>
<td colSpan={13}>{getMessage('common.message.no.data')}</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
<div className="chart-table-wrap">
<div className="sub-table-box">
<div className="module-table ">
<table className="small">
<thead>
<tr>
<th>{getMessage('simulator.table.sub1')}</th>
<th>{getMessage('simulator.table.sub2')}</th>
<th>{getMessage('simulator.table.sub3')}</th>
<th>{getMessage('simulator.table.sub4')}</th>
<th>{getMessage('simulator.table.sub5')}</th>
</tr>
</thead>
<tbody>
{moduleInfoList.length > 0 ? (
moduleInfoList.map((moduleInfo) => {
return (
<>
<tr key={moduleInfo.itemId}>
{/* 지붕면 */}
<td>{moduleInfo.roofSurface}</td>
{/* 경사각 */}
<td>{convertNumberToPriceDecimal(moduleInfo.slope)}</td>
{/* 방위각(도) */}
<td>{convertNumberToPriceDecimal(moduleInfo.angle)}</td>
{/* 태양전지모듈 */}
<td>
<div className="overflow-lab">{moduleInfo.itemNo}</div>
</td>
{/* 매수 */}
<td>{moduleInfo.amount}</td>
</tr>
</>
)
})
) : (
<tr>
<td colSpan={5}>{getMessage('common.message.no.data')}</td>
</tr>
)}
</tbody>
</table>
{moduleInfoList.length > 0 && (
<div className="module-total">
<div className="total-title">{getMessage('simulator.table.sub6')}</div>
<div className="total-num">
{moduleInfoList.reduce((acc, moduleInfo) => convertNumberToPriceDecimal(Number(acc) + Number(moduleInfo.amount)), 0)}
</div>
</div>
)}
</div>
</div>
<div className="sub-table-box">
<div className="module-table ">
<table className="big">
<thead>
<tr>
<th>{getMessage('simulator.table.sub7')}</th>
<th>{getMessage('simulator.table.sub8')}</th>
</tr>
</thead>
<tbody>
{pcsInfoList.length > 0 ? (
pcsInfoList.map((pcsInfo) => {
return (
<>
<tr key={pcsInfo.itemId}>
{/* 파워컨디셔너 */}
<td className="al-l">
<div className="overflow-lab">{pcsInfo.itemNo}</div>
</td>
{/* 대 */}
<td>{pcsInfo.amount}</td>
</tr>
</>
)
})
) : (
<tr>
<td colSpan={2}>{getMessage('common.message.no.data')}</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div className="sub-content-box">
<div className="sub-table-box">
<div className="simulation-guide-wrap">
<div className="simulation-tit-wrap">
<span>
{getMessage('simulator.notice.sub1')}
<br />
{getMessage('simulator.notice.sub2')}
</span>
</div>
{/* 시뮬레이션 안내사항 */}
<div
className="simulation-guide-box"
dangerouslySetInnerHTML={{
__html: content,
}}
></div>
</div>
</div>
</div>
</div>
</div>
)
}

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal'
import { settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
import { corridorDimensionSelector, settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
import { setSurfaceShapePattern } from '@/util/canvas-util'
import { POLYGON_TYPE } from '@/common/common'
@ -17,6 +17,8 @@ export function useCanvasSetting() {
const { option1, option2, dimensionDisplay } = settingModalFirstOptions
const { option3, option4 } = settingModalSecondOptions
const corridorDimension = useRecoilValue(corridorDimensionSelector)
const globalLocaleState = useRecoilValue(globalLocaleStore)
const { get, post } = useAxios(globalLocaleState)
const { getMessage } = useMessage()
@ -27,6 +29,36 @@ export function useCanvasSetting() {
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요
useEffect(() => {
if (!canvas) {
return
}
const { column } = corridorDimension
const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText')
switch (column) {
case 'corridorDimension':
lengthTexts.forEach((obj) => {
if (obj.planeSize) {
obj.set({ text: obj.planeSize.toString() })
}
})
break
case 'realDimension':
lengthTexts.forEach((obj) => {
if (obj.actualSize) {
obj.set({ text: obj.actualSize.toString() })
}
})
break
case 'noneDimension':
lengthTexts.forEach((obj) => {
obj.set({ text: '' })
})
break
}
canvas.renderAll()
}, [corridorDimension])
useEffect(() => {
console.log('useCanvasSetting useEffect 실행1')
fetchSettings()
@ -257,7 +289,7 @@ export function useCanvasSetting() {
optionName = ['7']
break
case 'flowDisplay': //흐름방향 표시
optionName = ['arrow']
optionName = ['arrow', 'flowText']
break
case 'trestleDisplay': //가대 표시
optionName = ['8']

View File

@ -1782,8 +1782,13 @@ export function useMode() {
}
wall.lines.forEach((line, index) => {
const lineLength = Math.sqrt(
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
)
line.attributes.roofId = roof.id
line.attributes.currentRoof = roof.lines[index].id
line.attributes.planeSize = lineLength
line.attributes.actualSize = lineLength
})
setRoof(roof)

View File

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

View File

@ -693,8 +693,7 @@ export const usePolygon = () => {
}
}
})
const direction = newRoofs.length === 1 ? polygon.direction : representLine.direction
const direction = polygon.direction ?? representLine.direction
const polygonDirection = polygon.direction
switch (direction) {
@ -723,7 +722,7 @@ export const usePolygon = () => {
originY: 'center',
selectable: true,
defense: defense,
direction: newRoofs.length === 1 ? polygonDirection : defense,
direction: polygonDirection ?? defense,
pitch: pitch,
})

View File

@ -824,7 +824,7 @@
"estimate.detail.estimateType": "注文分類",
"estimate.detail.roofCns": "屋根材・仕様施工",
"estimate.detail.remarks": "備考",
"estimate.detail.nextSubmit": "後日資料提出",
"estimate.detail.fileFlg": "後日資料提出",
"estimate.detail.header.fileList1": "ファイル添付",
"estimate.detail.fileList.btn": "ファイル選択",
"estimate.detail.header.fileList2": "添付ファイル一覧",
@ -841,11 +841,60 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
"estimate.detail.header.showPrice": "価格表示",
"estimate.detail.showPrice.btn1": "Pricing",
"estimate.detail.header.unitPrice": "定価",
"estimate.detail.showPrice.pricingBtn": "Pricing",
"estimate.detail.showPrice.description1": "製品価格 OPEN",
"estimate.detail.showPrice.description2": "追加, 変更資材",
"estimate.detail.showPrice.description3": "添付必須",
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
"estimate.detail.showPrice.btn2": "製品を追加",
"estimate.detail.showPrice.btn3": "製品削除"
"estimate.detail.showPrice.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": "シミュレーション案内事項"
}

View File

@ -830,7 +830,7 @@
"estimate.detail.estimateType": "주문분류",
"estimate.detail.roofCns": "지붕재・사양시공",
"estimate.detail.remarks": "비고",
"estimate.detail.nextSubmit": "후일자료제출",
"estimate.detail.fileFlg": "후일자료제출",
"estimate.detail.header.fileList1": "파일첨부",
"estimate.detail.fileList.btn": "파일선택",
"estimate.detail.header.fileList2": "첨부파일 목록",
@ -847,11 +847,60 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
"estimate.detail.header.showPrice": "가격표시",
"estimate.detail.showPrice.btn1": "Pricing",
"estimate.detail.header.unitPrice": "정가",
"estimate.detail.showPrice.pricingBtn": "Pricing",
"estimate.detail.showPrice.description1": "제품 가격 OPEN",
"estimate.detail.showPrice.description2": "추가, 변경 자재",
"estimate.detail.showPrice.description3": "첨부필수",
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
"estimate.detail.showPrice.btn2": "제품추가",
"estimate.detail.showPrice.btn3": "제품삭제"
"estimate.detail.showPrice.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": "시뮬레이션 안내사항"
}

View File

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

View File

@ -1,13 +1,6 @@
import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine'
import {
calculateIntersection,
distanceBetweenPoints,
findClosestPoint,
getDegreeByChon,
getDirectionByPoint,
isPointOnLine,
} from '@/util/canvas-util'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'
import * as turf from '@turf/turf'
@ -1307,14 +1300,19 @@ const drawRidge = (roof, canvas) => {
prevRoof = index === 0 ? wallLines[wallLines.length - 1] : wallLines[index - 1]
nextRoof = index === wallLines.length - 1 ? wallLines[0] : index === wallLines.length ? wallLines[1] : wallLines[index + 1]
if (prevRoof.direction !== nextRoof.direction && currentWall.length <= currentRoof.length) {
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.length })
const angle1 = calculateAngle(prevRoof.startPoint, prevRoof.endPoint)
const angle2 = calculateAngle(nextRoof.startPoint, nextRoof.endPoint)
if (Math.abs(angle1 - angle2) === 180 && currentWall.attributes.planeSize <= currentRoof.attributes.planeSize) {
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize })
}
})
// 지붕의 길이가 짧은 순으로 정렬
ridgeRoof.sort((a, b) => a.length - b.length)
console.log('ridgeRoof', ridgeRoof)
ridgeRoof.forEach((item) => {
if (getMaxRidge(roofLines.length) > roof.ridges.length) {
let index = item.index,
@ -1336,28 +1334,23 @@ const drawRidge = (roof, canvas) => {
let xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)), //x가 같은 내부선
yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선
let ridgeBaseLength = Math.round((currentRoof.length / 2) * 10) / 10, // 지붕의 기반 길이
ridgeMaxLength = Math.min(prevRoof.length, nextRoof.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
ridgeAcrossLength = Math.round((ridgeMaxLength - currentRoof.length) * 10) / 10 // 맞은편 벽까지의 길이 - 지붕의 기반 길이
let ridgeBaseLength = Math.round(currentRoof.attributes.planeSize / 2), // 지붕의 기반 길이
ridgeMaxLength = Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
ridgeAcrossLength = Math.abs(Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) - currentRoof.attributes.planeSize) // 맞은편 벽까지의 길이 - 지붕의 기반 길이
let acrossRoof = anotherRoof
.filter((roof) => {
if (roof.x1 === roof.x2) {
if ((nextRoof.direction === 'right' && roof.x1 > currentRoof.x1) || (nextRoof.direction === 'left' && roof.x1 < currentRoof.x1)) {
return roof
}
}
if (roof.y1 === roof.y2) {
if ((nextRoof.direction === 'top' && roof.y1 < currentRoof.y1) || (nextRoof.direction === 'bottom' && roof.y1 > currentRoof.y1)) {
return roof
}
const angle1 = calculateAngle(currentRoof.startPoint, currentRoof.endPoint)
const angle2 = calculateAngle(roof.startPoint, roof.endPoint)
if (Math.abs(angle1 - angle2) === 180) {
return roof
}
})
.reduce((prev, current) => {
let hasBetweenRoof = false
if (current.x1 === current.x2) {
hasBetweenRoof = roofLines
.filter((roof) => roof !== current && roof !== currentRoof)
.filter((roof) => roof !== current)
.some((line) => {
let currentY2 = currentRoof.y2
if (yEqualInnerLines.length > 0) {
@ -1369,12 +1362,13 @@ const drawRidge = (roof, canvas) => {
const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentRoof.y1)
const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < current.x1) || (line.x1 > currentRoof.x1 && line.x1 < current.x1)
const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1)
return isY1Between && isY2Between && isX1Between && isX2Between
})
}
if (current.y1 === current.y2) {
hasBetweenRoof = wallLines
.filter((roof) => roof !== current && roof !== currentRoof)
hasBetweenRoof = roofLines
.filter((roof) => roof !== current)
.some((line) => {
let currentX2 = currentRoof.x2
if (xEqualInnerLines.length > 0) {
@ -1412,20 +1406,23 @@ const drawRidge = (roof, canvas) => {
}
}
}, undefined)
if (acrossRoof !== undefined) {
if (currentRoof.x1 === currentRoof.x2) {
if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) {
ridgeAcrossLength = Math.round((Math.round(Math.abs(currentRoof.x1 - acrossRoof.x1) * 10) / 10 - currentRoof.length) * 10) / 10
ridgeAcrossLength = Math.round(Math.round(Math.abs(currentRoof.x1 - acrossRoof.x1) * 10) - currentRoof.attributes.planeSize)
}
}
if (currentRoof.y1 === currentRoof.y2) {
if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) {
ridgeAcrossLength = Math.round((Math.round(Math.abs(currentRoof.y1 - acrossRoof.y1) * 10) / 10 - currentRoof.length) * 10) / 10
ridgeAcrossLength = Math.round(Math.round(Math.abs(currentRoof.y1 - acrossRoof.y1) * 10) - currentRoof.attributes.planeSize)
}
}
}
ridgeBaseLength = ridgeBaseLength / 10
ridgeMaxLength = ridgeMaxLength / 10
ridgeAcrossLength = ridgeAcrossLength / 10
if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) {
let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
if (currentRoof.x1 === currentRoof.x2) {

View File

@ -527,6 +527,11 @@
resolved "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz"
integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA==
"@kurkle/color@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
@ -4337,6 +4342,13 @@ chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chart.js@^4.4.6:
version "4.4.6"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.6.tgz#da39b84ca752298270d4c0519675c7659936abec"
integrity sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==
dependencies:
"@kurkle/color" "^0.3.0"
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
@ -5832,6 +5844,11 @@ rbush@^3.0.1:
dependencies:
quickselect "^2.0.0"
react-chartjs-2@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d"
integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==
react-color-palette@^7.2.2:
version "7.2.2"
resolved "https://registry.npmjs.org/react-color-palette/-/react-color-palette-7.2.2.tgz"