448 lines
16 KiB
JavaScript

'use client'
import 'chart.js/auto'
import { Bar } from 'react-chartjs-2'
import dayjs from 'dayjs'
import { v4 as uuidv4 } from 'uuid'
import { useEffect, useState, useRef, useContext } from 'react'
import { useRecoilState } from 'recoil'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
import { pwrGnrSimTypeState } from '@/store/simulatorAtom'
import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage'
import { convertNumberToPriceDecimal } from '@/util/common-utils'
import { usePlan } from '@/hooks/usePlan'
import { usePopup } from '@/hooks/usePopup'
import { QcastContext } from '@/app/QcastProvider'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
export default function Simulator() {
// global 로딩바
const { setIsGlobalLoading } = useContext(QcastContext)
const { floorPlanState } = useContext(FloorPlanContext)
const { objectNo, pid } = floorPlanState
const { selectedPlan } = usePlan()
const chartRef = useRef(null)
// 캔버스 메뉴 넘버 셋팅
const { closeAll } = usePopup()
const { get } = useAxios()
const { getMessage } = useMessage()
const { setSelectedMenu } = useCanvasMenu()
// 차트 관련
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).map((item) => {
const num = Number(item.replaceAll(',', ''))
return isNaN(num) ? 0 : num
}),
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(() => {
setSelectedMenu('simulation')
/* 초기화 작업 */
setChartData([])
setObjectDetail({})
setModuleInfoList([])
setPcsInfoList([])
setHatsudenryouAll([])
setHatsudenryouAllSnow([])
setHatsudenryouPeakcutAll([])
setHatsudenryouPeakcutAllSnow([])
if (objectNo && pid && selectedPlan) {
fetchObjectDetail(objectNo, selectedPlan?.planNo??pid)
fetchSimulatorNotice()
setPwrGnrSimType('D')
setPwrRecoil({ ...pwrRecoil, type: 'D' })
closeAll()
}
}, [objectNo, pid, selectedPlan])
// 물건 상세 정보 조회
const [objectDetail, setObjectDetail] = useState({})
// 모듈배치정보 조회
const [moduleInfoList, setModuleInfoList] = useState([])
// 파워컨디셔너 조회
const [pcsInfoList, setPcsInfoList] = useState([])
// 타입별 list 조회
const [hatsudenryouAll, setHatsudenryouAll] = useState([])
const [hatsudenryouAllSnow, setHatsudenryouAllSnow] = useState([])
const [hatsudenryouPeakcutAll, setHatsudenryouPeakcutAll] = useState([])
const [hatsudenryouPeakcutAllSnow, setHatsudenryouPeakcutAllSnow] = useState([])
const fetchObjectDetail = async (objectNo, currentPid) => {
setIsGlobalLoading(true)
const apiUrl = `/api/pwrGnrSimulation/calculations?objectNo=${objectNo}&planNo=${currentPid}`
const resultData = await get({ url: apiUrl })
if (resultData) {
setObjectDetail(resultData)
if (resultData.hatsudenryouAll) {
setHatsudenryouAll(resultData.hatsudenryouAll)
}
if (resultData.hatsudenryouAllSnow) {
setHatsudenryouAllSnow(resultData.hatsudenryouAllSnow)
}
if (resultData.hatsudenryouPeakcutAll) {
setHatsudenryouPeakcutAll(resultData.hatsudenryouPeakcutAll)
}
if (resultData.hatsudenryouPeakcutAllSnow) {
setHatsudenryouPeakcutAllSnow(resultData.hatsudenryouPeakcutAllSnow)
setChartData(resultData.hatsudenryouPeakcutAllSnow)
}
if (resultData.pcsList) {
setPcsInfoList(resultData.pcsList)
}
if (resultData.roofModuleList) {
setModuleInfoList(resultData.roofModuleList)
}
}
setIsGlobalLoading(false)
}
// 시뮬레이션 안내사항 조회
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'))
}
})
}
// 차트 데이터 변경 시, list type 셋팅
const [pwrGnrSimType, setPwrGnrSimType] = useState('D')
const [pwrRecoil, setPwrRecoil] = useRecoilState(pwrGnrSimTypeState)
const handleChartChangeData = (type) => {
setPwrRecoil({ ...pwrRecoil, type: type })
switch (type) {
case 'A':
setChartData(hatsudenryouAll)
break
case 'B':
setChartData(hatsudenryouPeakcutAll)
break
case 'C':
setChartData(hatsudenryouAllSnow)
break
case 'D':
setChartData(hatsudenryouPeakcutAllSnow)
break
}
}
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}
{`${objectDetail.planNo ? `(Plan No: ${objectDetail.planNo})` : ''}`}
</div>
</div>
{/* 작성일 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub2')}</div>
<div className="estimate-name">
{objectDetail.drawingEstimateCreateDate ? `${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">{objectDetail.capacity ? `${convertNumberToPriceDecimal(objectDetail.capacity)} kW` : ''}</div>
</div>
{/* 연간예측발전량 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub4')}</div>
<div className="estimate-name">{chartData[chartData.length - 1]}</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 ? `${objectDetail.snowfall}cm` : ''} </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">
{/* chart */}
<div className="select-wrap">
<select
style={{ width: '30%' }}
className="select-light"
value={pwrGnrSimType}
defaultValue={`D`}
onChange={(e) => {
handleChartChangeData(e.target.value)
setPwrGnrSimType(e.target.value)
}}
>
<option value={`A`}>積雪考慮なし(ピークカットなし発電量)</option>
<option value={`B`}>積雪考慮なし(ピークカットあり発電量)</option>
<option value={`C`}>積雪考慮あり(ピークカットなし発電量)</option>
<option value={`D`}>積雪考慮あり(ピークカットあり発電量)</option>
</select>
</div>
<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={uuidv4()}>{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.slopeAngle)}
{moduleInfo.classType == 0 ? '寸' : 'º'}
</td>
{/* 방위각(도) */}
<td>{convertNumberToPriceDecimal(moduleInfo.azimuth)}</td>
{/* 태양전지모듈 */}
<td>
<div className="overflow-lab">{moduleInfo.itemNo}</div>
</td>
{/* 매수 */}
<td>{convertNumberToPriceDecimal(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>{convertNumberToPriceDecimal(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>
)
}