1040 lines
35 KiB
JavaScript
1040 lines
35 KiB
JavaScript
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
|
import { useContext, useEffect, useRef, useState } from 'react'
|
|
import PowerConditionalSelect from '@/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect'
|
|
import StepUp from '@/components/floor-plan/modal/circuitTrestle/step/StepUp'
|
|
import { useMessage } from '@/hooks/useMessage'
|
|
import { usePopup } from '@/hooks/usePopup'
|
|
import PassivityCircuitAllocation from './step/type/PassivityCircuitAllocation'
|
|
import BasicSetting from '@/components/floor-plan/modal/basic/BasicSetting'
|
|
import { useMasterController } from '@/hooks/common/useMasterController'
|
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
|
import { GlobalDataContext } from '@/app/GlobalDataProvider'
|
|
import { POLYGON_TYPE, MENU } from '@/common/common'
|
|
import { useSwal } from '@/hooks/useSwal'
|
|
import { canvasState, canvasZoomState, currentMenuState } from '@/store/canvasAtom'
|
|
|
|
import { useTrestle } from '@/hooks/module/useTrestle'
|
|
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
import { useEstimate } from '@/hooks/useEstimate'
|
|
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
|
|
import { useImgLoader } from '@/hooks/floorPlan/useImgLoader'
|
|
import { QcastContext } from '@/app/QcastProvider'
|
|
import { fontSelector } from '@/store/fontAtom'
|
|
import { fabric } from 'fabric'
|
|
|
|
const ALLOCATION_TYPE = {
|
|
AUTO: 'auto',
|
|
PASSIVITY: 'passivity',
|
|
}
|
|
export default function CircuitTrestleSetting({ id }) {
|
|
const { getMessage } = useMessage()
|
|
const { closePopup, addPopup } = usePopup()
|
|
const { apply, setViewCircuitNumberTexts, getEstimateData, clear: clearTrestle, setAllModuleSurfaceIsComplete } = useTrestle()
|
|
const { swalFire } = useSwal()
|
|
const { saveEstimate } = useEstimate()
|
|
const canvas = useRecoilValue(canvasState)
|
|
const setCurrentMenu = useSetRecoilState(currentMenuState)
|
|
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
|
|
const [tabNum, setTabNum] = useState(1)
|
|
const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO)
|
|
const [circuitAllocationType, setCircuitAllocationType] = useState(1)
|
|
const { managementState, setManagementState } = useContext(GlobalDataContext)
|
|
const selectedModules = useRecoilValue(selectedModuleState)
|
|
const { getPcsAutoRecommendList, getPcsVoltageChk, getPcsVoltageStepUpList, getPcsManualConfChk } = useMasterController()
|
|
const flowText = useRecoilValue(fontSelector('flowText'))
|
|
const lengthText = useRecoilValue(fontSelector('lengthText'))
|
|
const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText'))
|
|
|
|
// 회로할당(승합설정)에서 선택된 값들을 저장할 상태 추가
|
|
const [selectedStepUpValues, setSelectedStepUpValues] = useState({})
|
|
const [getStepUpSelections, setGetStepUpSelections] = useState(null)
|
|
// const { trigger: setCircuitData } = useCanvasPopupStatusController(4)
|
|
// const [stepUpListData, setStepUpListData] = useRecoilState(stepUpListDataState)
|
|
const [stepUpListData, setStepUpListData] = useState([])
|
|
const [seletedMainOption, setSeletedMainOption] = useState(null)
|
|
const [seletedSubOption, setSeletedSubOption] = useState(null)
|
|
const { setModuleStatisticsData } = useCircuitTrestle()
|
|
const { handleCanvasToPng } = useImgLoader()
|
|
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
|
|
const passivityCircuitAllocationRef = useRef()
|
|
const { setIsGlobalLoading } = useContext(QcastContext)
|
|
|
|
const originCanvasViewPortTransform = useRef([])
|
|
const [isFold, setIsFold] = useState(false)
|
|
|
|
const {
|
|
makers,
|
|
setMakers,
|
|
selectedMaker,
|
|
setSelectedMaker,
|
|
series,
|
|
setSeries,
|
|
models,
|
|
setModels,
|
|
selectedModels,
|
|
setSelectedModels,
|
|
pcsCheck,
|
|
setPcsCheck,
|
|
getOptYn,
|
|
getPcsItemList,
|
|
getSelectedPcsItemList,
|
|
getUseModuleItemList,
|
|
getRoofSurfaceList,
|
|
getModuleList,
|
|
removeNotAllocationModules,
|
|
resetCircuits,
|
|
} = useCircuitTrestle()
|
|
// const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
|
|
useEffect(() => {
|
|
originCanvasViewPortTransform.current = [...canvas.viewportTransform]
|
|
if (!managementState) {
|
|
}
|
|
// setCircuitData({
|
|
// makers,
|
|
// selectedMaker,
|
|
// series,
|
|
// models,
|
|
// selectedModels,
|
|
// pcsCheck,
|
|
// })
|
|
|
|
return () => {
|
|
const moduleSetupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
|
if (moduleSetupSurfaces.some((surface) => !surface.isComplete)) {
|
|
resetCircuits()
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
// 모듈이 설치된 경우 rack설치 여부에 따라 설치 된 모듈 아래에 작은 모듈이 설치되어 있을 경우는 모듈 설치 메뉴 reopen
|
|
useEffect(() => {
|
|
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
if (modules.length === 0) {
|
|
return
|
|
}
|
|
|
|
/**
|
|
* 랙 설치 시 모듈 크기 검증
|
|
* - 남쪽: 아래쪽 모듈이 위쪽 모듈보다 커야 함
|
|
* - 북쪽: 위쪽 모듈이 아래쪽 모듈보다 커야 함
|
|
* - 동쪽: 오른쪽 모듈이 왼쪽 모듈보다 커야 함
|
|
* - 서쪽: 왼쪽 모듈이 오른쪽 모듈보다 커야 함
|
|
*/
|
|
const validateModuleSizeForRack = (surface) => {
|
|
const { modules, direction, trestleDetail } = surface
|
|
const { rackYn, moduleIntvlHor, moduleIntvlVer } = trestleDetail
|
|
|
|
if (rackYn === 'N' || !modules || modules.length < 2) {
|
|
return true // 검증 통과
|
|
}
|
|
|
|
// 모듈 중심점 및 크기 정보 계산
|
|
const centerPoints = modules.map((module) => {
|
|
const { x, y } = module.getCenterPoint()
|
|
const { width, height } = module
|
|
return {
|
|
x,
|
|
y,
|
|
width: Math.floor(width),
|
|
height: Math.floor(height),
|
|
area: Math.floor(width) * Math.floor(height),
|
|
}
|
|
})
|
|
|
|
// 방향별 설정
|
|
const isVertical = direction === 'south' || direction === 'north'
|
|
const primaryInterval = isVertical ? moduleIntvlVer : moduleIntvlHor
|
|
const secondaryInterval = isVertical ? moduleIntvlHor : moduleIntvlVer
|
|
|
|
// 정렬 함수: 큰 모듈이 있어야 할 위치부터 시작
|
|
const getSortFn = () => {
|
|
switch (direction) {
|
|
case 'south': return (a, b) => b.y - a.y // 아래쪽(y 큼)부터
|
|
case 'north': return (a, b) => a.y - b.y // 위쪽(y 작음)부터
|
|
case 'east': return (a, b) => b.x - a.x // 오른쪽(x 큼)부터
|
|
case 'west': return (a, b) => a.x - b.x // 왼쪽(x 작음)부터
|
|
default: return () => 0
|
|
}
|
|
}
|
|
|
|
// 타겟 모듈 찾기 (현재 모듈보다 작아야 할 위치의 모듈)
|
|
const findTargetModules = (current, margin) => {
|
|
return centerPoints.filter(cp => {
|
|
const sameAxis = isVertical
|
|
? Math.abs(cp.x - current.x) < margin
|
|
: Math.abs(cp.y - current.y) < margin
|
|
const targetDirection = direction === 'south' ? cp.y < current.y
|
|
: direction === 'north' ? cp.y > current.y
|
|
: direction === 'east' ? cp.x < current.x
|
|
: cp.x > current.x
|
|
return sameAxis && targetDirection
|
|
})
|
|
}
|
|
|
|
// 가장 가까운 타겟 모듈 찾기
|
|
const getClosestTarget = (filtered) => {
|
|
if (filtered.length === 0) return null
|
|
return filtered.reduce((closest, cp) => {
|
|
switch (direction) {
|
|
case 'south': return cp.y > closest.y ? cp : closest
|
|
case 'north': return cp.y < closest.y ? cp : closest
|
|
case 'east': return cp.x > closest.x ? cp : closest
|
|
case 'west': return cp.x < closest.x ? cp : closest
|
|
default: return closest
|
|
}
|
|
})
|
|
}
|
|
|
|
// 두 모듈 간 간격 계산
|
|
const getGap = (current, target) => {
|
|
if (isVertical) {
|
|
return direction === 'south'
|
|
? (current.y - current.height / 2) - (target.y + target.height / 2)
|
|
: (target.y - target.height / 2) - (current.y + current.height / 2)
|
|
} else {
|
|
return direction === 'east'
|
|
? (current.x - current.width / 2) - (target.x + target.width / 2)
|
|
: (target.x - target.width / 2) - (current.x + current.width / 2)
|
|
}
|
|
}
|
|
|
|
// 인접 모듈 여부 확인
|
|
const isAdjacent = (current, target) => {
|
|
const gap = getGap(current, target)
|
|
return gap >= 0 && gap <= primaryInterval + 1
|
|
}
|
|
|
|
// 정렬된 모듈 순회
|
|
const sortedPoints = [...centerPoints].sort(getSortFn())
|
|
|
|
for (const current of sortedPoints) {
|
|
// 1. 일반 배치: 같은 라인에서 인접 모듈 검사
|
|
const directTargets = findTargetModules(current, secondaryInterval)
|
|
const closestTarget = getClosestTarget(directTargets)
|
|
|
|
if (closestTarget && isAdjacent(current, closestTarget) && closestTarget.area > current.area) {
|
|
return false // 검증 실패
|
|
}
|
|
|
|
// 2. 물떼새 배치: 반 오프셋 위치의 인접 모듈 검사
|
|
const size = isVertical ? current.width : current.height
|
|
const halfOffset = (size + secondaryInterval) / 2
|
|
|
|
for (const sign of [-1, 1]) {
|
|
const offsetValue = sign * halfOffset
|
|
const findHalfTarget = centerPoints.filter((cp) => {
|
|
const offsetAxis = isVertical
|
|
? Math.abs(cp.x - (current.x + offsetValue)) <= primaryInterval
|
|
: Math.abs(cp.y - (current.y + offsetValue)) <= primaryInterval
|
|
const targetDirection = direction === 'south' ? cp.y < current.y
|
|
: direction === 'north' ? cp.y > current.y
|
|
: direction === 'east' ? cp.x < current.x
|
|
: cp.x > current.x
|
|
return offsetAxis && targetDirection
|
|
})
|
|
|
|
const closestHalf = getClosestTarget(findHalfTarget)
|
|
if (closestHalf && isAdjacent(current, closestHalf) && closestHalf.area > current.area) {
|
|
return false // 검증 실패
|
|
}
|
|
}
|
|
}
|
|
|
|
return true // 검증 통과
|
|
}
|
|
|
|
// 모든 설치면에 대해 검증 수행
|
|
const moduleSetupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
|
for (const surface of moduleSetupSurfaces) {
|
|
if (!validateModuleSizeForRack(surface)) {
|
|
swalFire({
|
|
text: getMessage('module.size.validation.rack.error'),
|
|
icon: 'error',
|
|
confirmFn: () => {
|
|
// 현재 팝업 닫기
|
|
closePopup(id)
|
|
// 메뉴 하이라이트 변경
|
|
setCurrentMenu(MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING)
|
|
// 모듈/가대설정 팝업 열기
|
|
const newPopupId = uuidv4()
|
|
clearTrestle()
|
|
setAllModuleSurfaceIsComplete(false)
|
|
addPopup(newPopupId, 1, <BasicSetting id={newPopupId} />)
|
|
},
|
|
})
|
|
return
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
const capture = async (type) => {
|
|
beforeCapture(type)
|
|
|
|
await handleCanvasToPng(type)
|
|
|
|
afterCapture(type)
|
|
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve(true)
|
|
}, 1000)
|
|
})
|
|
}
|
|
|
|
// 캡쳐 전 처리
|
|
const beforeCapture = (type) => {
|
|
setCanvasZoom(100)
|
|
canvas.set({ zoom: 1 })
|
|
|
|
// roof 객체들을 찾아서 중앙점 계산
|
|
const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof' && !obj.wall)
|
|
|
|
if (roofs.length > 0) {
|
|
// 모든 roof의 x, y 좌표를 수집
|
|
const allPoints = []
|
|
roofs.forEach((roof) => {
|
|
if (roof.getCurrentPoints()) {
|
|
roof.getCurrentPoints().forEach((point) => {
|
|
allPoints.push({ x: point.x, y: point.y })
|
|
})
|
|
}
|
|
})
|
|
|
|
if (allPoints.length > 0) {
|
|
// 모든 점들의 중앙값 계산
|
|
const minX = Math.min(...allPoints.map((p) => p.x))
|
|
const maxX = Math.max(...allPoints.map((p) => p.x))
|
|
const minY = Math.min(...allPoints.map((p) => p.y))
|
|
const maxY = Math.max(...allPoints.map((p) => p.y))
|
|
|
|
const centerX = (minX + maxX) / 2
|
|
const centerY = (minY + maxY) / 2
|
|
|
|
// 캔버스 중앙으로 이동하기 위한 오프셋 계산
|
|
const canvasWidth = canvas.getWidth()
|
|
const canvasHeight = canvas.getHeight()
|
|
const offsetX = canvasWidth / 2 - centerX
|
|
const offsetY = canvasHeight / 2 - centerY
|
|
|
|
canvas.viewportTransform = [1, 0, 0, 1, offsetX, offsetY]
|
|
} else {
|
|
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
|
|
}
|
|
} else {
|
|
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
|
|
}
|
|
|
|
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
const circuitNumberTexts = canvas.getObjects().filter((obj) => obj.name === 'circuitNumber')
|
|
|
|
if (type === 2) {
|
|
modules.forEach((module) => {
|
|
module.set({ originColor: module.fill, fill: null, strokeWidth: 2 })
|
|
})
|
|
circuitNumberTexts.forEach((text) => {
|
|
text.set({ visible: false })
|
|
})
|
|
}
|
|
|
|
let x, y
|
|
x = canvas.width / 2
|
|
y = canvas.height / 2
|
|
|
|
canvas.zoomToPoint(new fabric.Point(x, y), 0.4)
|
|
|
|
changeFontSize('lengthText', '28')
|
|
changeFontSize('circuitNumber', '28')
|
|
changeFontSize('flowText', '28')
|
|
canvas.renderAll()
|
|
}
|
|
|
|
// 캡쳐 후 처리
|
|
const afterCapture = (type) => {
|
|
if (originCanvasViewPortTransform.current[0] !== 1) {
|
|
setCanvasZoom(Number((originCanvasViewPortTransform.current[0] * 100).toFixed(0)))
|
|
}
|
|
canvas.viewportTransform = [...originCanvasViewPortTransform.current]
|
|
canvas.renderAll()
|
|
|
|
changeFontSize('lengthText', lengthText.fontSize.value)
|
|
changeFontSize('circuitNumber', circuitNumberText.fontSize.value)
|
|
changeFontSize('flowText', flowText.fontSize.value)
|
|
|
|
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
const circuitNumberTexts = canvas.getObjects().filter((obj) => obj.name === 'circuitNumber')
|
|
if (type === 2) {
|
|
modules.forEach((module) => {
|
|
module.set({
|
|
fill: module.originColor,
|
|
strokeWidth: 0.3,
|
|
})
|
|
})
|
|
circuitNumberTexts.forEach((text) => {
|
|
text.set({ visible: true })
|
|
})
|
|
}
|
|
canvas.renderAll()
|
|
}
|
|
|
|
// 수동할당 시 모듈 삭제
|
|
|
|
// 시리즈중 자동으로 추천 PCS 정보 조회
|
|
const onAutoRecommend = () => {
|
|
if (series.filter((s) => s.selected).length === 0) {
|
|
swalFire({
|
|
title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error02'),
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
const isMultiModule = selectedModules.itemList.length > 1
|
|
|
|
let isAllIndfcs = false
|
|
|
|
if (isMultiModule) {
|
|
//INDFCS 실내집중, OUTDMULTI 옥외멀티
|
|
// 1. 모듈이 혼합형일 경우 선택한 pcs가 실내집중인 경우 alert
|
|
if (selectedModels.length > 0) {
|
|
isAllIndfcs = selectedModels.every((model) => model.pcsTpCd === 'INDFCS')
|
|
} else {
|
|
isAllIndfcs = models.every((model) => model.pcsTpCd === 'INDFCS')
|
|
}
|
|
}
|
|
|
|
if (isAllIndfcs) {
|
|
swalFire({
|
|
title: getMessage('module.circuit.indoor.focused.error'),
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
const params = {
|
|
...getOptYn(),
|
|
useModuleItemList: getUseModuleItemList(),
|
|
roofSurfaceList: getRoofSurfaceList(),
|
|
pcsItemList: getPcsItemList(isMultiModule),
|
|
}
|
|
|
|
// 파워컨디셔너 추천 목록 조회
|
|
if (selectedModels.length === 0) {
|
|
// 시리즈중 자동으로 추천 PCS 정보 조회
|
|
getPcsAutoRecommendList(params).then((res) => {
|
|
if (res.data?.pcsItemList) {
|
|
let pcsItemList = []
|
|
let pcsObj = {}
|
|
|
|
models.forEach((model) => {
|
|
pcsObj[model.itemId] = model
|
|
})
|
|
res.data?.pcsItemList.forEach((item) => {
|
|
if (pcsObj[item.itemId]) {
|
|
pcsItemList.push({
|
|
...pcsObj[item.itemId],
|
|
isUsed: false,
|
|
id: uuidv4(),
|
|
})
|
|
}
|
|
})
|
|
// 회로 구성 가능 여부 체크 요청 파라미터
|
|
const pcsVoltageChkParams = {
|
|
...getOptYn(),
|
|
useModuleItemList: getUseModuleItemList(),
|
|
roofSurfaceList: getRoofSurfaceList(),
|
|
pcsItemList: pcsItemList,
|
|
}
|
|
// 추천 목록 선택
|
|
setSelectedModels(pcsItemList)
|
|
// 회로 구성 가능 여부 체크
|
|
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
|
|
if (res.resultCode === 'S') {
|
|
setTabNum(2)
|
|
setAllModuleSurfaceIsComplete(false)
|
|
} else {
|
|
swalFire({
|
|
title: res.resultMsg,
|
|
type: 'alert',
|
|
})
|
|
}
|
|
})
|
|
} else {
|
|
// 데이터가 없는 경우 오류 메시지 확인 필요
|
|
if (res.result.resultCode === 'E') {
|
|
swalFire({
|
|
title: res.result.resultMsg,
|
|
type: 'alert',
|
|
})
|
|
} else {
|
|
swalFire({
|
|
title: '파워컨디셔너를 추가해 주세요.',
|
|
type: 'alert',
|
|
})
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
// 회로 구성 가능 여부 체크
|
|
getPcsVoltageChk({ ...params, pcsItemList: getSelectedPcsItemList(isMultiModule) }).then((res) => {
|
|
if (res.resultCode === 'S') {
|
|
// 회로 구성 가능 여부 체크 통과 시 승압설정 정보 조회
|
|
getPcsVoltageStepUpList({
|
|
...params,
|
|
pcsItemList: getSelectedPcsItemList(isMultiModule),
|
|
}).then((res) => {
|
|
if (res?.result.resultCode === 'S' && res?.data) {
|
|
setTabNum(2)
|
|
setAllModuleSurfaceIsComplete(false)
|
|
} else {
|
|
swalFire({ text: getMessage('common.message.send.error') })
|
|
}
|
|
})
|
|
} else {
|
|
swalFire({
|
|
title: res.resultMsg,
|
|
type: 'alert',
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 자동할당 버튼 클릭 시
|
|
const onAutoAllocation = () => {
|
|
let moduleStdQty = 0
|
|
let moduleMaxQty = 0
|
|
const selectedModels = models.filter((m) => m.selected)
|
|
|
|
// 시리즈중 자동으로 추천 PCS 정보 조회
|
|
if (selectedModels.length === 0) {
|
|
onAutoRecommend()
|
|
} else {
|
|
// 모듈 최소 매수
|
|
moduleStdQty = selectedModels.reduce((acc, model) => {
|
|
return acc + parseInt(model.moduleStdQty)
|
|
}, 0)
|
|
moduleMaxQty = selectedModels.reduce((acc, model) => {
|
|
return acc + parseInt(model.moduleMaxQty)
|
|
}, 0)
|
|
}
|
|
// const target = pcsCheck.max ? moduleMaxQty : moduleStdQty
|
|
// const placementModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
|
|
// if (placementModules.length > target) {
|
|
// swalFire({
|
|
// title: '배치가능 매수를 초과합니다. 파워컨디셔너를 다시 선택해 주세요.',
|
|
// type: 'alert',
|
|
// })
|
|
// return
|
|
// }
|
|
|
|
// setAllocationType(ALLOCATION_TYPE.AUTO)
|
|
// setTabNum(2)
|
|
}
|
|
|
|
// 수동할당 버튼 클릭 시
|
|
const onPassivityAllocation = () => {
|
|
if (selectedModels.length === 0) {
|
|
const params = {
|
|
...getOptYn(),
|
|
useModuleItemList: getUseModuleItemList(),
|
|
roofSurfaceList: getRoofSurfaceList().map((surface) => {
|
|
return {
|
|
...surface,
|
|
moduleList: surface.moduleList.map((module) => {
|
|
return {
|
|
itemId: module.itemId,
|
|
uniqueId: module.uniqueId,
|
|
}
|
|
}),
|
|
}
|
|
}),
|
|
pcsItemList: getPcsItemList(),
|
|
}
|
|
|
|
// 파워컨디셔너 추천 목록 조회
|
|
getPcsAutoRecommendList(params).then((res) => {
|
|
if (res.data?.pcsItemList) {
|
|
let pcsItemList = []
|
|
let pcsObj = {}
|
|
models.forEach((model) => {
|
|
pcsObj[model.itemId] = model
|
|
})
|
|
res.data?.pcsItemList.forEach((item) => {
|
|
if (pcsObj[item.itemId]) {
|
|
pcsItemList.push({
|
|
...pcsObj[item.itemId],
|
|
isUsed: false,
|
|
id: uuidv4(),
|
|
})
|
|
}
|
|
})
|
|
const pcsVoltageChkParams = {
|
|
...getOptYn(),
|
|
useModuleItemList: getUseModuleItemList(),
|
|
roofSurfaceList: getRoofSurfaceList(),
|
|
pcsItemList: pcsItemList.map((item) => {
|
|
return {
|
|
itemId: item.itemId,
|
|
pcsMkrCd: item.pcsMkrCd,
|
|
pcsSerCd: item.pcsSerCd,
|
|
}
|
|
}),
|
|
}
|
|
setSelectedModels(pcsItemList)
|
|
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
|
|
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
|
|
setAllModuleSurfaceIsComplete(false)
|
|
clearTrestle()
|
|
})
|
|
} else {
|
|
swalFire({
|
|
title: res.result.resultMsg,
|
|
type: 'alert',
|
|
confirmFn: () => {
|
|
return
|
|
},
|
|
})
|
|
return
|
|
}
|
|
})
|
|
} else {
|
|
const moduleStdQty = selectedModels.reduce((acc, model) => {
|
|
return acc + parseInt(model.moduleStdQty)
|
|
}, 0)
|
|
const moduleMaxQty = selectedModels.reduce((acc, model) => {
|
|
return acc + parseInt(model.moduleMaxQty)
|
|
}, 0)
|
|
|
|
const target = pcsCheck.max ? moduleMaxQty : moduleStdQty
|
|
const placementModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
let moduleAmount = placementModules.reduce((acc, module) => {
|
|
if (moduleSelectionData.module.itemList.length === 1 || module.moduleInfo.itemId === moduleSelectionData.module.itemList[0].itemId) {
|
|
return acc + 1
|
|
} else {
|
|
return acc + 0.66
|
|
}
|
|
}, 0)
|
|
|
|
if (moduleAmount > target) {
|
|
swalFire({
|
|
title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error01'),
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
|
|
clearTrestle()
|
|
}
|
|
}
|
|
|
|
// StepUp에서 선택된 값들을 처리하는 함수 수정
|
|
const handleStepUpValuesSelected = (selectionData) => {
|
|
const { gooodsNo } = selectionData
|
|
|
|
setSelectedStepUpValues((prev) => ({
|
|
...prev,
|
|
[gooodsNo]: selectionData,
|
|
}))
|
|
}
|
|
|
|
// StepUp 컴포넌트 초기화 핸들러
|
|
const handleStepUpInitialize = (getCurrentSelections) => {
|
|
setGetStepUpSelections(() => getCurrentSelections)
|
|
}
|
|
|
|
// 회로할당(승압설정) 저장 버튼 클릭 시
|
|
const onApply = async () => {
|
|
setAllModuleSurfaceIsComplete(false)
|
|
|
|
setIsGlobalLoading(true)
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
|
.map((obj) => {
|
|
obj.pcses = getStepUpListData()
|
|
})
|
|
await capture(1)
|
|
|
|
//회로할당 저장 시 result=null인 경우에도 회로번호 텍스트 표시 유지 처리
|
|
|
|
// 회로할당 저장 클릭 시
|
|
// 가대 및 지지금구 설치
|
|
// apply()
|
|
// apply() 결과를 체크하여 null이면 함수 종료
|
|
const applyResult = apply()
|
|
if (!applyResult) {
|
|
setIsGlobalLoading(false)
|
|
return
|
|
}
|
|
|
|
const result = await getEstimateData()
|
|
|
|
if (result) {
|
|
await capture(2)
|
|
// 견적서 저장
|
|
await saveEstimate(result)
|
|
} else {
|
|
setIsGlobalLoading(false)
|
|
}
|
|
|
|
// removeNotAllocationModules()
|
|
}
|
|
|
|
const changeFontSize = (name, size) => {
|
|
const textObjs = canvas?.getObjects().filter((obj) => obj.name === name)
|
|
textObjs.forEach((obj) => {
|
|
obj.set({
|
|
fontSize: size,
|
|
})
|
|
})
|
|
canvas.renderAll()
|
|
}
|
|
|
|
// 이전 버튼 클릭 시
|
|
const onClickPrev = () => {
|
|
// setAllocationType(ALLOCATION_TYPE.AUTO)
|
|
swalFire({
|
|
text: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset.info'),
|
|
type: 'alert',
|
|
icon: 'warning',
|
|
confirmFn: () => {
|
|
const circuitModules = canvas.getObjects().filter((obj) => obj.name === 'module')
|
|
canvas.remove(...circuitModules.map((module) => module.circuit))
|
|
circuitModules.forEach((obj) => {
|
|
obj.circuit = null
|
|
obj.pcsItemId = null
|
|
obj.circuitNumber = null
|
|
obj.pcs = null
|
|
})
|
|
setSelectedModels(
|
|
JSON.parse(JSON.stringify(selectedModels)).map((model) => {
|
|
model.isUsed = false
|
|
return model
|
|
}),
|
|
)
|
|
|
|
if (allocationType === ALLOCATION_TYPE.PASSIVITY) {
|
|
setAllocationType(ALLOCATION_TYPE.AUTO)
|
|
setTabNum(1)
|
|
} else {
|
|
setTabNum(1)
|
|
}
|
|
clearTrestle()
|
|
|
|
canvas.renderAll()
|
|
setModuleStatisticsData()
|
|
},
|
|
})
|
|
}
|
|
|
|
// 파워컨디셔너 컴포넌트 속성
|
|
const powerConditionalSelectProps = {
|
|
tabNum,
|
|
setTabNum,
|
|
makers,
|
|
setMakers,
|
|
selectedMaker,
|
|
setSelectedMaker,
|
|
series,
|
|
setSeries,
|
|
models,
|
|
setModels,
|
|
selectedModels,
|
|
setSelectedModels,
|
|
managementState,
|
|
getUseModuleItemList,
|
|
}
|
|
|
|
// 수동할당 컴포넌트 속성
|
|
const passivityProps = {
|
|
tabNum,
|
|
setTabNum,
|
|
pcsCheck,
|
|
selectedModels,
|
|
setSelectedModels,
|
|
getOptYn,
|
|
getUseModuleItemList,
|
|
getRoofSurfaceList,
|
|
}
|
|
|
|
// 승압설정 컴포넌트 속성
|
|
const stepUpProps = {
|
|
tabNum,
|
|
setTabNum,
|
|
models,
|
|
setModels,
|
|
allocationType,
|
|
circuitAllocationType,
|
|
setCircuitAllocationType,
|
|
selectedMaker,
|
|
selectedModels,
|
|
setSelectedModels,
|
|
getSelectedPcsItemList,
|
|
getOptYn, // 옵션 Y/N
|
|
getUseModuleItemList, // 사용된 모듈아이템 List
|
|
getRoofSurfaceList, // 지붕면 목록
|
|
getPcsItemList, // PCS 아이템 목록
|
|
onValuesSelected: handleStepUpValuesSelected, // 선택된 값들을 처리하는 함수
|
|
stepUpListData,
|
|
setStepUpListData,
|
|
seletedMainOption,
|
|
setSeletedMainOption,
|
|
seletedSubOption,
|
|
setSeletedSubOption,
|
|
getModuleList,
|
|
setModuleStatisticsData,
|
|
}
|
|
|
|
// 승압설정 목록 조회
|
|
const getStepUpListData = () => {
|
|
const pcs = []
|
|
console.log(stepUpListData)
|
|
stepUpListData[0].pcsItemList.map((item, index) => {
|
|
return item.serQtyList
|
|
.filter((serQty) => serQty.selected && serQty.paralQty > 0)
|
|
.forEach((serQty) => {
|
|
pcs.push({
|
|
pcsMkrCd: item.pcsMkrCd,
|
|
pcsSerCd: item.pcsSerCd,
|
|
pcsItemId: item.itemId,
|
|
pscOptCd: getPcsOptCd(index),
|
|
paralQty: serQty.paralQty,
|
|
connections: item.connList?.length
|
|
? [
|
|
{
|
|
connItemId: item.connList[0].itemId,
|
|
},
|
|
]
|
|
: [],
|
|
})
|
|
// return {
|
|
// pcsMkrCd: item.pcsMkrCd,
|
|
// pcsSerCd: item.pcsSerCd,
|
|
// pcsItemId: item.itemId,
|
|
// pscOptCd: getPcsOptCd(index),
|
|
// paralQty: serQty.paralQty,
|
|
// connections: item.connList?.length
|
|
// ? [
|
|
// {
|
|
// connItemId: item.connList[0].itemId,
|
|
// },
|
|
// ]
|
|
// : [],
|
|
// }
|
|
})
|
|
})
|
|
return pcs
|
|
}
|
|
|
|
const getPcsOptCd = (index) => {
|
|
console.log(selectedModels)
|
|
console.log(seletedMainOption)
|
|
console.log(seletedSubOption)
|
|
if (selectedModels.some((model) => model.pcsSerParallelYn === 'Y')) {
|
|
if (selectedModels.length > 1) {
|
|
if (index === 0) {
|
|
return seletedMainOption.code
|
|
} else {
|
|
return seletedSubOption.code
|
|
}
|
|
} else {
|
|
return seletedMainOption.code
|
|
}
|
|
} else {
|
|
return seletedMainOption.code
|
|
}
|
|
}
|
|
|
|
const handleStepUp = () => {
|
|
handlePassivityAllocationCkeck()
|
|
// const notAllocationModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && !obj.circuit)
|
|
// if (notAllocationModules.length > 0) {
|
|
// swalFire({
|
|
// title: getMessage('not.allocation.exist.module'),
|
|
// type: 'alert',
|
|
// })
|
|
// return
|
|
// } else {
|
|
// passivityCircuitAllocationRef.current.onApply()
|
|
// setTabNum(2)
|
|
// }
|
|
}
|
|
|
|
// 닫기 버튼 클릭 시 처리하는 함수 추가
|
|
const handleClose = () => {
|
|
// // 회로 번호 텍스트 제거
|
|
if (
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
|
.some((surface) => !surface.isComplete)
|
|
) {
|
|
canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'circuitNumber'))
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
.forEach((obj) => {
|
|
obj.circuit = null
|
|
obj.pcsItemId = null
|
|
obj.circuitNumber = null
|
|
})
|
|
|
|
canvas.renderAll()
|
|
}
|
|
|
|
closePopup(id)
|
|
}
|
|
|
|
const handlePassivityAllocationCkeck = () => {
|
|
let pcsCount = {}
|
|
let result = {}
|
|
const notAllocationModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && !obj.circuit)
|
|
if (notAllocationModules.length > 0) {
|
|
swalFire({
|
|
text: getMessage('not.allocation.exist.module'),
|
|
type: 'alert',
|
|
icon: 'warning',
|
|
})
|
|
return
|
|
}
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
|
.forEach((module) => {
|
|
const circuitNumber = module.circuitNumber.replace(/[()]/g, '')
|
|
pcsCount[circuitNumber] = (pcsCount[circuitNumber] || 0) + 1
|
|
})
|
|
for (const key in pcsCount) {
|
|
const firstPart = key.split('-')[0] // '-' 기준으로 첫 번째 부분을 추출
|
|
const value = pcsCount[key]
|
|
|
|
// 그룹이 없으면 초기화
|
|
if (!result[firstPart]) {
|
|
result[firstPart] = { maxValue: value, count: 1 }
|
|
} else {
|
|
// 이미 그룹이 있으면 큰 값으로 갱신, count는 증가
|
|
result[firstPart].maxValue = Math.max(result[firstPart].maxValue, value)
|
|
result[firstPart].count += 1
|
|
}
|
|
}
|
|
const params = {
|
|
...getOptYn(),
|
|
useModuleItemList: getUseModuleItemList(),
|
|
roofSurfaceList: getRoofSurfaceList(),
|
|
pcsItemList: selectedModels.map((model, index) => {
|
|
return {
|
|
pcsMkrCd: model.pcsMkrCd,
|
|
pcsSerCd: model.pcsSerCd,
|
|
itemId: model.itemId,
|
|
itemNm: model.itemNm,
|
|
goodsNo: model.goodsNo,
|
|
serQtyList: [
|
|
{
|
|
serQty: result[(index + 1).toString()] ? result[(index + 1).toString()].maxValue : 0,
|
|
paralQty: result[(index + 1).toString()] ? result[(index + 1).toString()].count : 0,
|
|
rmdYn: 'Y',
|
|
usePossYn: 'Y',
|
|
roofSurfaceList: canvas
|
|
.getObjects()
|
|
.filter((obj) => POLYGON_TYPE.MODULE_SETUP_SURFACE === obj.name && obj?.modules.length > 0)
|
|
.map((surface) => {
|
|
return {
|
|
roofSurfaceId: surface.id,
|
|
roofSurface: surface.direction,
|
|
roofSurfaceIncl: +canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].pitch,
|
|
moduleList: surface.modules.map((module) => {
|
|
return {
|
|
itemId: module.moduleInfo.itemId,
|
|
circuit: module.circuitNumber,
|
|
pcsItemId: module.pcsItemId,
|
|
}
|
|
}),
|
|
}
|
|
}),
|
|
},
|
|
],
|
|
}
|
|
}),
|
|
}
|
|
|
|
getPcsManualConfChk(params).then((res) => {
|
|
if (res?.resultCode === 'E') {
|
|
swalFire({
|
|
text: res.resultMsg,
|
|
type: 'alert',
|
|
icon: 'warning',
|
|
})
|
|
return
|
|
} else {
|
|
setTabNum(2)
|
|
setAllModuleSurfaceIsComplete(false)
|
|
}
|
|
})
|
|
}
|
|
|
|
return (
|
|
<WithDraggable isShow={true} pos={{ x: 50, y: 230 }} className="l-2">
|
|
<WithDraggable.Header
|
|
title={getMessage('modal.circuit.trestle.setting')}
|
|
onClose={() => handleClose()}
|
|
isFold={isFold}
|
|
onFold={() => setIsFold(!isFold)}
|
|
/>
|
|
<WithDraggable.Body>
|
|
<div style={{ display: !(tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY) && isFold ? 'none' : 'block' }}>
|
|
<div style={{ display: tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && isFold ? 'none' : 'block' }}>
|
|
<div className="roof-module-tab">
|
|
<div className={`module-tab-bx act`}>{getMessage('modal.circuit.trestle.setting.power.conditional.select')}</div>
|
|
<span className={`tab-arr ${tabNum === 2 ? 'act' : ''}`}></span>
|
|
<div className={`module-tab-bx ${tabNum === 2 ? 'act' : ''}`}>
|
|
{getMessage('modal.circuit.trestle.setting.circuit.allocation')}({getMessage('modal.circuit.trestle.setting.step.up.allocation')})
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && <PowerConditionalSelect {...powerConditionalSelectProps} />}
|
|
{tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && (
|
|
<PassivityCircuitAllocation {...passivityProps} ref={passivityCircuitAllocationRef} isFold={isFold} />
|
|
)}
|
|
{tabNum === 2 && <StepUp {...stepUpProps} onInitialize={handleStepUpInitialize} />}
|
|
</div>
|
|
|
|
{tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && (
|
|
<div className="grid-btn-wrap">
|
|
<button className="btn-frame modal mr5 act" onClick={() => onAutoRecommend()}>
|
|
{getMessage('modal.circuit.trestle.setting.circuit.allocation.auto')}
|
|
</button>
|
|
<button className="btn-frame modal act" onClick={() => onPassivityAllocation()}>
|
|
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
{tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && (
|
|
<div className="grid-btn-wrap">
|
|
<button className="btn-frame modal mr5 " onClick={() => onClickPrev()}>
|
|
{getMessage('modal.common.prev')}
|
|
</button>
|
|
<button className="btn-frame modal act" onClick={() => handleStepUp()}>
|
|
{getMessage('modal.circuit.trestle.setting.circuit.allocation')}({getMessage('modal.circuit.trestle.setting.step.up.allocation')})
|
|
</button>
|
|
</div>
|
|
)}
|
|
{tabNum === 2 && (
|
|
<div className="grid-btn-wrap">
|
|
<button className="btn-frame modal mr5" onClick={() => onClickPrev()}>
|
|
{getMessage('modal.common.prev')}
|
|
</button>
|
|
{/* <button className="btn-frame modal act" onClick={() => apply()}> */}
|
|
<button className="btn-frame modal act" onClick={onApply}>
|
|
{getMessage('modal.common.save')}({getMessage('modal.circuit.trestle.setting.circuit.allocation')})
|
|
</button>
|
|
</div>
|
|
)}
|
|
</WithDraggable.Body>
|
|
</WithDraggable>
|
|
)
|
|
}
|