qcast-front/src/hooks/usePlan.js

741 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useContext, useEffect, useState } from 'react'
import { usePathname, useRouter } from 'next/navigation'
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil'
import {
canvasState,
currentCanvasPlanState,
plansState,
canvasSettingState,
currentObjectState,
moduleSetupSurfaceState,
currentMenuState,
} from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
import { POLYGON_TYPE, SAVE_KEY } from '@/common/common'
import { removeImage } from '@/lib/fileAction'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { outerLinePointsState } from '@/store/outerLineAtom'
import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom'
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { compasDegAtom } from '@/store/orientationAtom'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { useCanvasPopupStatusController } from './common/useCanvasPopupStatusController'
import { useCanvasMenu } from './common/useCanvasMenu'
import { QcastContext } from '@/app/QcastProvider'
import { unescapeString } from '@/util/common-utils'
import { useTrestle } from '@/hooks/module/useTrestle'
import { logger } from '@/util/logger'
// putCanvasStatus 의 in-flight 요청 — module-scope 로 두어 usePlan() 인스턴스 간 공유.
// 큰 fabric JSON 의 동시 PUT 이 SQL Server 데드락 victim 을 양산해 온 이력 때문에 직전 호출은 abort.
const canvasStatusInflight = { controller: null }
/**
* 플랜 처리 훅
* 플랜을 표시하는 탭 UI 전반적인 처리 로직 관리
* @param {*} params
* @returns
*/
export function usePlan(params = {}) {
const { floorPlanState } = useContext(FloorPlanContext)
const [selectedPlan, setSelectedPlan] = useState(null)
const setCurrentMenu = useSetRecoilState(currentMenuState)
const [canvas, setCanvas] = useRecoilState(canvasState)
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
const [plans, setPlans] = useRecoilState(plansState)
const router = useRouter()
const pathname = usePathname()
const { swalFire } = useSwal()
const { getMessage } = useMessage()
const { get, post, promisePost, promisePut, promiseDel, promiseGet } = useAxios()
const { setEstimateContextState, handleDeleteEstimate, handleEstimateImageCopy } = useEstimateController()
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
const { fetchBasicSettings, basicSettingCopySave } = useCanvasSetting()
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
/** 전역 로딩바 컨텍스트 */
const { setIsGlobalLoading } = useContext(QcastContext)
/**
* 플랜 복사 시 모듈이 있을경우 모듈 데이터 복사하기 위한 처리
*/
const { getModuleSelection } = useCanvasPopupStatusController()
const [compasDeg, setCompasDeg] = useRecoilState(compasDegAtom)
const [moduleSelectionDataStore, setModuleSelectionDataStore] = useRecoilState(moduleSelectionDataState)
const [selectedModules, setSelectedModules] = useRecoilState(selectedModuleState)
const { selectedMenu, setSelectedMenu } = useCanvasMenu()
//선택된 객체 초기화
const resetCurrentObject = useResetRecoilState(currentObjectState)
//선택된 모듈 배치면 초기화
const resetModuleSetupSurface = useResetRecoilState(moduleSetupSurfaceState)
const { isAllComplete } = useTrestle()
/**
* 마우스 포인터의 가이드라인 제거
*/
const removeMouseLines = () => {
if (canvas?._objects.length > 0) {
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((item) => canvas?.remove(item))
}
canvas?.renderAll()
}
/**
* 현재 캔버스 데이터를 JSON 문자열로 직렬화
*
* @returns {string} 직렬화된 캔버스 데이터
*/
const addCanvas = () => {
const objs = canvas?.toJSON(SAVE_KEY)
const str = JSON.stringify(objs)
return str
}
/**
* 현재 캔버스에 그려진 데이터를 추출
*
* @param {string} mode
* @returns {string} 캔버스 데이터
*/
const currentCanvasData = (mode = '') => {
removeMouseLines()
canvas.discardActiveObject()
if (mode === 'save') {
const groups = canvas.getObjects().filter((obj) => obj.type === 'group')
if (groups.length > 0) {
groups.forEach((group) => {
canvas?.remove(group)
canvas?.renderAll()
const restore = group._restoreObjectsState() //그룹 좌표 복구
//그룹시 좌표가 틀어지는 이슈
restore._objects.forEach((obj) => {
obj.set({
...obj,
groupYn: true,
groupName: group.name,
groupId: group.id,
})
//디렉션이 있는 경우에만
if (group.lineDirection) obj.set({ lineDirection: group.lineDirection })
//부모객체가 있으면 (면형상 위에 도머등..)
if (group.parentId) obj.set({ parentId: group.parentId })
canvas?.add(obj)
obj.setCoords()
canvas?.renderAll()
})
})
}
}
return addCanvas()
}
/**
* DB에 저장된 데이터를 canvas에서 사용할 수 있도록 포맷화
*
* @param {string} cs - DB에 저장된 캔버스 데이터 문자열
* @returns {string} 캔버스에서 사용할 수 있도록 포맷팅된 문자열
*/
const dbToCanvasFormat = (cs) => {
return cs.replace(/##/g, '"').replace(/∠/g, '∠').replace(/°/g, '°')
}
/**
* canvas의 데이터를 DB에 저장할 수 있도록 포맷화
*
* @param {string} cs - 캔버스 데이터 문자열
* @returns {string} DB 저장용 포맷 문자열
*/
const canvasToDbFormat = (cs) => {
return cs.replace(/"/g, '##')
}
/**
* 페이지 내 캔버스를 저장
*
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
*/
const saveCanvas = async (saveAlert = true) => {
// [PLAN-CORRUPT-COORD-SAVE-GUARD 2026-05-18] 손상 plan(예: S241X135260518004) 저장 차단형 가드.
// 그림의 목적: 지붕→모듈→견적→사진. 깨진 모듈도 사용자가 그린 데이터 → 자동 제거 금지.
// 손상 객체 발견 시 저장 차단 + swal 안내 → 사용자가 화면에서 복원/재배치/명시적 삭제 결정.
// 향후 [PLAN-CORRUPT-RESTORE] 작업으로 메타 데이터(top/height/surfaceId)에서 좌표 복원 예정.
const corruptedObjs = canvas.getObjects().filter((obj) => obj.__corrupted)
if (corruptedObjs.length > 0) {
// 자세한 객체별 진단 (top/height/fill/moduleInfo/points-sample 까지 — 복원 단서로 사용)
const detail = corruptedObjs.map((o) => ({
id: o.id,
name: o.name,
parentId: o.parentId,
surfaceId: o.surfaceId,
top: o.top,
left: o.left,
width: o.width,
height: o.height,
fill: typeof o.fill === 'string' ? o.fill : o.fill?.type,
opacity: o.opacity,
direction: o.direction,
moduleInfo: o.moduleInfo
? { itemId: o.moduleInfo.itemId, moduleTpCd: o.moduleInfo.moduleTpCd, capacity: o.moduleInfo.capacity }
: null,
pointsSample: Array.isArray(o.points) ? o.points.slice(0, 4) : null,
}))
logger.error('[PLAN-CORRUPT-COORD-SAVE-GUARD 2026-05-18] 保存失敗 — corrupted objects', {
count: corruptedObjs.length,
objectNo: currentCanvasPlan?.objectNo,
planNo: currentCanvasPlan?.planNo,
detail,
})
swalFire({
icon: 'error',
title: '保存に失敗しました',
text: `破損したモジュール ${corruptedObjs.length} 件が含まれています。\n再配置または明示的に削除してから再度保存してください。\n(詳細はコンソールログをご確認ください)`,
})
return
}
// [ROOF-MATERIAL-SAVE-GUARD 2026-05-08] 면(roof) 에 시각적 지붕재 패턴은 칠해져 있는데
// 데이터(roofMaterial) 가 비어있는 "불일치" 케이스만 저장 차단.
// - fill 비어있고 roofMaterial 도 없음 → 미선택 상태, 저장 허용
// - fill 이 fabric.Pattern 인데 roofMaterial 누락 → 저장 시 누락 JSON 이 DB 로 가고
// 재로드 시 setSurfaceShapePattern 0×0 patternCanvas → drawImage InvalidStateError
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && !obj.wall)
const inconsistent = roofs.filter((r) => {
const f = r.fill
const hasPatternFill = f && typeof f === 'object' && (f.source || f.type === 'pattern')
return hasPatternFill && !r.roofMaterial
})
if (inconsistent.length > 0) {
console.warn('[ROOF-MATERIAL-SAVE-GUARD] pattern drawn but roofMaterial missing:',
inconsistent.map((r) => ({ id: r.id, name: r.name, fillType: r.fill?.type })))
swalFire({ text: '面に屋根材パターンが描画されていますが、屋根材データが保存されていません。\n屋根材を再設定してから保存してください。', icon: 'warning' })
return
}
// 저장 전 선택되어 있는 object 제거
const setupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
setupSurfaces.forEach((surface) => {
surface.set({ fill: 'rgba(255,255,255,0.1)', strokeDashArray: [10, 4], strokeWidth: 1 })
})
const canvasStatus = currentCanvasData('save')
const result = await putCanvasStatus(canvasStatus, saveAlert)
//캔버스 저장 완료 후
if (result && !isAllComplete()) {
handleDeleteEstimate(currentCanvasPlan)
}
}
/**
* objectNo에 해당하는 canvas 목록을 조회
*
* @param {string} objectNo - 물건번호
* @param {string} planNo - 플랜번호
* @returns {Promise<Array>} canvas 목록
*/
const getCanvasByObjectNo = async (objectNo, planNo) => {
return await get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` }).then((res) =>
res.map((item) => ({
id: item.id,
objectNo: item.objectNo,
planNo: item.planNo,
userId: item.userId,
canvasStatus: dbToCanvasFormat(item.canvasStatus),
isCurrent: planNo === item.planNo,
bgImageName: item.bgImageName,
mapPositionAddress: item.mapPositionAddress,
})),
)
}
/**
* 신규 plan 추가
*
* @param {string} userId - 사용자 ID
* @param {string} objectNo - 물건번호
* @param {boolean} isCopy - 복제 여부
* @param {boolean} isInitPlan - 초기 플랜 생성 여부
*
* case 1) 초기 플랜 생성 : isInitPlan = true, isCopy = false
* case 2) 빈 플랜 생성 : isInitPlan = false, isCopy = false
* case 3) 복제 플랜 생성 : isInitPlan = false, isCopy = true
*/
const postObjectPlan = async (userId, objectNo, isCopy = false, isInitPlan = false) => {
const planData = isCopy
? {
userId: userId,
objectNo: objectNo,
copyFlg: '1',
planNo: currentCanvasPlan?.planNo,
}
: {
userId: userId,
objectNo: objectNo,
copyFlg: '0',
}
const res = await promisePost({ url: '/api/object/add-plan', data: planData })
let newPlan = {
id: res.data.canvasId,
objectNo: objectNo,
planNo: res.data.planNo,
userId: userId,
canvasStatus: '',
isCurrent: true,
bgImageName: null,
mapPositionAddress: null,
}
if (isInitPlan) {
/* 초기 플랜 생성인 경우 플랜 목록 초기화 */
setCurrentCanvasPlan(newPlan)
setPlans([newPlan])
/* 플랜 추가 시 배치면초기설정 정보 조회 */
fetchBasicSettings(newPlan.planNo, null, objectNo)
} else {
if (isCopy) {
const currentSelectedMenu = selectedMenu
/* 복제 플랜 생성인 경우 현재 캔버스 데이터를 복제 */
newPlan.canvasStatus = currentCanvasData()
newPlan.bgImageName = currentCanvasPlan?.bgImageName ?? null
newPlan.mapPositionAddress = currentCanvasPlan?.mapPositionAddress ?? null
/* 복제 시 배치면 초기설정 복사 */
await basicSettingCopySave({
...canvasSetting,
planNo: newPlan.planNo,
selectedRoofMaterial: {
...canvasSetting.selectedRoofMaterial,
planNo: newPlan.planNo,
},
roofsData: canvasSetting.roofsData.map((roof) => ({
...roof,
planNo: newPlan.planNo,
})),
})
/**
* 방위 데이터 복사
*/
const sourceDegree = await getModuleSelection(1)
console.log('🚀 ~ sourceDegree:', sourceDegree)
const degreeData = {
objectNo,
planNo: parseInt(newPlan.planNo),
popupType: 1,
popupStatus: unescapeString(sourceDegree.popupStatus),
}
console.log('🚀 ~ postObjectPlan ~ degreeData:', degreeData)
await post({ url: `/api/v1/canvas-popup-status`, data: degreeData })
/** 리코일 세팅 */
setCompasDeg(sourceDegree.popupStatus)
/**
* 모듈 선택 데이터 복사
*/
const moduleSelectionData = await getModuleSelection(2)
console.log('🚀 ~ moduleSelectionData:', moduleSelectionData)
const moduleStatus = moduleSelectionData.popupStatus.replace(/&quot;/g, '\"')
const moduleData = {
objectNo,
planNo: parseInt(newPlan.planNo),
popupType: 2,
popupStatus: moduleStatus,
}
console.log('🚀 ~ postObjectPlan ~ moduleData:', moduleData)
await post({ url: `/api/v1/canvas-popup-status`, data: moduleData })
const copyData = JSON.parse(moduleStatus)
console.log('🚀 ~ postObjectPlan ~ copyData:', copyData)
/** 리코일 세팅 */
setModuleSelectionDataStore(copyData)
if (copyData.module) setSelectedModules(copyData.module)
setSelectedMenu(currentSelectedMenu)
//이미지 복사
handleEstimateImageCopy(planData.objectNo, planData.planNo, planData.objectNo, newPlan.planNo)
} else {
setSelectedMenu('placement')
}
setCurrentCanvasPlan(newPlan)
setPlans((plans) => [...plans.map((plan) => ({ ...plan, isCurrent: false })), newPlan])
swalFire({ text: getMessage('plan.message.save') })
}
}
/**
* id에 해당하는 canvas 데이터를 수정
*
* @param {string} canvasStatus - 캔버스 데이터
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
*/
const putCanvasStatus = async (canvasStatus, saveAlert = true) => {
let rtn = false
const planData = {
id: currentCanvasPlan.id,
bgImageName: currentCanvasPlan?.bgImageName ?? null,
mapPositionAddress: currentCanvasPlan?.mapPositionAddress ?? null,
canvasStatus: canvasToDbFormat(canvasStatus),
}
// 직전 in-flight 가 살아있다면 취소. abort 된 호출은 .catch 에서 silent return.
if (canvasStatusInflight.controller) {
canvasStatusInflight.controller.abort()
}
const controller = new AbortController()
canvasStatusInflight.controller = controller
await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData, option: { signal: controller.signal } })
.then((res) => {
setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)))
if (saveAlert) swalFire({ text: getMessage('plan.message.save') })
rtn = true
})
.catch((error) => {
// AbortController 로 인한 취소는 정상 흐름 — 새 호출이 진행 중이므로 UX 메시지/로딩 해제 안 함
if (error?.name === 'CanceledError' || error?.code === 'ERR_CANCELED' || error?.name === 'AbortError') return
swalFire({ text: error.message, icon: 'error' })
setIsGlobalLoading(false)
})
.finally(() => {
if (canvasStatusInflight.controller === controller) {
canvasStatusInflight.controller = null
}
})
return rtn
}
/**
* id에 해당하는 canvas 데이터를 삭제
*
* @param {string} id - canvas ID
* @returns {Promise<boolean>} 결과
*/
const delCanvasById = async (id) => {
return await promiseDel({ url: `/api/canvas-management/canvas-statuses/by-id/${id}` })
}
/**
* objectNo에 해당하는 canvas 데이터들을 삭제
*
* @param {string} objectNo - 물건번호
* @returns {Promise<boolean>} 결과
*/
const delCanvasByObjectNo = async (objectNo) => {
return await promiseDel({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` })
}
/**
* plan 이동
* 현재 plan의 작업상태를 저장 후 이동
*
* @param {string} newCurrentId - 이동할 plan ID
*/
const handleCurrentPlan = async (newCurrentId) => {
const planNo = plans?.find((obj) => obj.id === newCurrentId).planNo
const objectNo = floorPlanState.objectNo
//견적서 or 발전시뮬
if (pathname !== '/floor-plan') {
await promiseGet({ url: `/api/estimate/${objectNo}/${planNo}/detail` })
.then((res) => {
if (res.status === 200) {
const estimateDetail = res.data
if (pathname === '/floor-plan/estimate/5') {
// 견적서 이동 조건 수정
// if (estimateDetail.tempFlg === '0' && estimateDetail.estimateDate !== null) {
if (estimateDetail.estimateDate !== null) {
res.data.resetFlag = 'N'
if (res.data.itemList.length > 0) {
res.data.itemList.map((item) => {
item.delFlg = '0'
})
}
if (res.data.pkgAsp === null || res.data.pkgAsp == undefined) {
res.data.pkgAsp = '0.00'
} else {
const number = parseFloat(res.data.pkgAsp)
const roundedNumber = isNaN(number) ? '0.00' : number.toFixed(2)
res.data.pkgAsp = roundedNumber.toString()
}
setEstimateContextState(res.data)
// 클릭한 플랜 탭으로 이동
setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId))
setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId })))
} else {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
}
} else {
// 발전시뮬레이션
if (estimateDetail.estimateDate !== null && estimateDetail.docNo) {
setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId))
setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId })))
} else {
swalFire({ text: getMessage('simulator.menu.move.valid1') })
}
}
}
})
.catch((error) => {
if (pathname === '/floor-plan/estimate/5') {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
} else {
swalFire({ text: getMessage('simulator.menu.move.valid1') })
}
})
} else {
swalFire({
text: getMessage('plan.message.confirm.save'),
type: 'confirm',
confirmFn: async () => {
//저장 전에 플랜이 이동되어 state가 변경되는 이슈가 있음
await saveCanvas(true)
clearRecoilState()
setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId))
setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId })))
},
denyFn: async () => {
setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId))
setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId })))
},
})
}
}
useEffect(() => {
setSelectedPlan(currentCanvasPlan)
handleCurrentPlanUrl()
// resetCurrentObject()
resetModuleSetupSurface()
}, [currentCanvasPlan])
/**
* clear가 필요한 recoil state 관리
*/
const clearRecoilState = () => {
resetOuterLinePoints()
resetPlacementShapeDrawingPoints()
}
/**
* 현재 plan의 정보를 URL에 추가
*/
const handleCurrentPlanUrl = () => {
if (currentCanvasPlan?.planNo && currentCanvasPlan?.objectNo)
router.push(`${pathname}?pid=${currentCanvasPlan?.planNo}&objectNo=${currentCanvasPlan?.objectNo}`)
}
/**
* 새로운 plan 생성
* 현재 plan의 데이터가 있을 경우 현재 plan 저장 및 복제 여부를 확인
*
* @param {string} userId - 사용자 ID
* @param {string} objectNo - 물건번호
*/
const handleAddPlan = async (userId, objectNo) => {
let isSelected = false
if (currentCanvasPlan?.id) {
swalFire({
text: getMessage('plan.message.confirm.save'),
type: 'confirm',
confirmFn: async () => {
//저장 전에 플랜이 이동되어 state가 변경되는 이슈가 있음
await saveCanvas(true)
handleAddPlanCopyConfirm(userId, objectNo)
},
denyFn: async () => {
handleAddPlanCopyConfirm(userId, objectNo)
},
})
}
}
const handleAddPlanCopyConfirm = async (userId, objectNo) => {
if (JSON.parse(currentCanvasData()).objects.length > 0) {
swalFire({
text: `Plan ${currentCanvasPlan.planNo} ` + getMessage('plan.message.confirm.copy'),
type: 'confirm',
confirmFn: async () => {
setIsGlobalLoading(true)
await postObjectPlan(userId, objectNo, true, false)
setIsGlobalLoading(false)
},
denyFn: async () => {
setIsGlobalLoading(true)
await postObjectPlan(userId, objectNo, false, false)
setIsGlobalLoading(false)
},
})
} else {
setIsGlobalLoading(true)
await postObjectPlan(userId, objectNo, false, false)
setIsGlobalLoading(false)
}
}
/**
* 물건번호(object) plan 삭제 (canvas 삭제 전 planNo 삭제)
*
* @param {string} userId - 사용자 ID
* @param {string} objectNo - 물건번호
* @param {string} planNo - 플랜번호
* @returns {Promise<boolean>} 결과
*/
const deleteObjectPlan = async (userId, objectNo, planNo) => {
return await promiseDel({ url: `/api/object/plan/${objectNo}/${planNo}?userId=${userId}` })
.then((res) => {
return true
})
.catch((error) => {
swalFire({ text: error.response.data.message, icon: 'error' })
return false
})
}
/**
* plan 삭제
*
* @param {Event} e - 이벤트
* @param {Object} targetPlan - 삭제할 plan
*/
const handleDeletePlan = async (e, targetPlan) => {
e.stopPropagation() // 이벤트 버블링 방지
const isSuccess = await deleteObjectPlan(targetPlan.id, targetPlan.objectNo, targetPlan.planNo)
if (!isSuccess) return
await delCanvasById(targetPlan.id)
.then((res) => {
setPlans((plans) => plans.filter((plan) => plan.id !== targetPlan.id))
removeImage(currentCanvasPlan.id)
/* 플랜 삭제 후 배치면 초기설정 삭제 */
deleteBasicSettings(targetPlan.objectNo, targetPlan.planNo)
swalFire({ text: getMessage('plan.message.delete') })
})
.catch((error) => {
swalFire({ text: error.message, icon: 'error' })
})
/* 삭제 후 last 데이터에 포커싱 */
const lastPlan = plans.filter((plan) => plan.id !== targetPlan.id).at(-1)
if (!lastPlan) {
setCurrentCanvasPlan(null)
} else if (targetPlan.id !== lastPlan.id) {
setCurrentCanvasPlan(lastPlan)
setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === lastPlan.id })))
/* 플랜 삭제 시 그 전 플랫의 배치면초기설정 정보 조회 */
fetchBasicSettings(lastPlan.planNo, null, lastPlan.objectNo)
}
}
/**
* plan 조회
*
* @param {string} userId - 사용자 ID
* @param {string} objectNo - 물건번호
* @param {string} planNo - 플랜번호
*/
const loadCanvasPlanData = async (userId, objectNo, planNo) => {
console.log('🚀 ~ loadCanvasPlanData ~ userId, objectNo, planNo:', userId, objectNo, planNo)
await getCanvasByObjectNo(objectNo, planNo).then(async (res) => {
if (res.length > 0) {
setCurrentCanvasPlan(res.find((plan) => plan.planNo === planNo))
setPlans(res)
} else {
await postObjectPlan(userId, objectNo, false, true)
}
})
}
/**
* plan canvasStatus 초기화
*/
const resetCanvasStatus = () => {
setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: null, objectNo: null, planNo: null, id: null }))
setPlans((plans) => plans.map((plan) => ({ ...plan, canvasStatus: null })))
setCurrentMenu(null)
setSelectedMenu(null)
}
/**
* plan canvasStatus 불러오기
* 견적서/발전시뮬레이션에서 플랜 이동 시 현재 플랜의 canvasStatus를 불러오기 위해 사용
*
* @param {string} objectNo - 물건번호
* @param {string} planNo - 플랜번호
*/
const reloadCanvasStatus = async (objectNo, planNo) => {
if (pathname === '/floor-plan/estimate/5' || pathname === '/floor-plan/simulator/6') {
await getCanvasByObjectNo(objectNo, planNo).then((res) => {
if (res.length > 0) {
// setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: res.find((plan) => plan.planNo === planNo).canvasStatus }))
// setPlans((plans) => plans.map((plan) => ({ ...plan, canvasStatus: res.find((resPlan) => resPlan.planNo === plan.planNo).canvasStatus })))
setCurrentCanvasPlan(res.find((plan) => plan.planNo === planNo))
setPlans(res)
}
})
}
}
/**
* 플랜 삭제 시 배치면 초기설정 데이터 삭제
*
* @param {string} objectNo - 물건번호
* @param {string} planNo - 플랜번호
*/
const deleteBasicSettings = async (objectNo, planNo) => {
try {
await promiseDel({ url: `/api/canvas-management/canvas-basic-settings/delete-basic-settings/${objectNo}/${planNo}` })
} catch (error) {
/* 오류를 무시하고 계속 진행 */
console.log('Basic settings delete failed or not found:', error)
// swalFire({ text: error.message, icon: 'error' })
}
}
return {
canvas,
plans,
currentCanvasPlan,
setCurrentCanvasPlan,
selectedPlan,
saveCanvas,
handleCurrentPlan,
handleAddPlan,
handleDeletePlan,
loadCanvasPlanData,
resetCanvasStatus,
reloadCanvasStatus,
}
}