diff --git a/public/static/images/canvas/roof_warning_correct.png b/public/static/images/canvas/roof_warning_correct.png new file mode 100644 index 00000000..4f9741a5 Binary files /dev/null and b/public/static/images/canvas/roof_warning_correct.png differ diff --git a/public/static/images/canvas/roof_warning_wrong.png b/public/static/images/canvas/roof_warning_wrong.png new file mode 100644 index 00000000..3a245d51 Binary files /dev/null and b/public/static/images/canvas/roof_warning_wrong.png differ diff --git a/src/app/api/image/estimate-image-copy/route.js b/src/app/api/image/estimate-image-copy/route.js new file mode 100644 index 00000000..d7c80b13 --- /dev/null +++ b/src/app/api/image/estimate-image-copy/route.js @@ -0,0 +1,59 @@ +import { NextResponse } from 'next/server' +import { S3Client, CopyObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3' +import sharp from 'sharp' +import { v4 as uuidv4 } from 'uuid' +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) + +export async function POST(req) { + const { objectNo, planNo, newObjectNo, newPlanNo } = await req.json() + + const responseArray = [] + + //견적서1 번 이미지 + const isExistImage1 = await s3.send( + new GetObjectCommand({ + Bucket, + Key: `Drawing/${objectNo}_${planNo}_1.png`, + }), + ) + + //견적서2 번 이미지 + const isExistImage2 = await s3.send( + new GetObjectCommand({ + Bucket, + Key: `Drawing/${objectNo}_${planNo}_2.png`, + }), + ) + + //견적서1,2 번 이미지 둘다 있어야함 + if (isExistImage1.$metadata.httpStatusCode === 200 && isExistImage2.$metadata.httpStatusCode === 200) { + //견적서1 번 이미지 복사 + const copyCommand = new CopyObjectCommand({ + Bucket, + CopySource: encodeURI(`${Bucket}/Drawing/${objectNo}_${planNo}_1.png`), + Key: `Drawing/${newObjectNo}_${newPlanNo}_1.png`, + }) + + const response = await s3.send(copyCommand) + + const copyCommand2 = new CopyObjectCommand({ + Bucket, + CopySource: encodeURI(`${Bucket}/Drawing/${objectNo}_${planNo}_2.png`), + Key: `Drawing/${newObjectNo}_${newPlanNo}_2.png`, + }) + + const response2 = await s3.send(copyCommand2) + + responseArray.push(response, response2) + return NextResponse.json({ message: '견적서 이미지 복사 성공', responseArray }, { status: 200 }) + } else { + return NextResponse.json({ message: '견적서 이미지 복사 실패(존재하지 않는 이미지)', responseArray }, { status: 400 }) + } +} diff --git a/src/app/floor-plan/FloorPlanProvider.js b/src/app/floor-plan/FloorPlanProvider.js index 5019e8a4..1e5c536d 100644 --- a/src/app/floor-plan/FloorPlanProvider.js +++ b/src/app/floor-plan/FloorPlanProvider.js @@ -45,8 +45,19 @@ const FloorPlanProvider = ({ children }) => { // const pathname = usePathname() // const setCorrentObjectNo = useSetRecoilState(correntObjectNoState) const searchParams = useSearchParams() + const path = usePathname() const objectNo = searchParams.get('objectNo') const pid = searchParams.get('pid') + + useEffect(() => { + setFloorPlanState((prev) => { + return { + ...prev, + objectNo, + pid, + } + }) + }, [path]) // useEffect(() => { // console.log('🚀 ~ useEffect ~ objectNo:') // if (pathname === '/floor-plan') { diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx index 7237e056..e59dd9ff 100644 --- a/src/components/estimate/Estimate.jsx +++ b/src/components/estimate/Estimate.jsx @@ -154,7 +154,7 @@ export default function Estimate({}) { useEffect(() => { // console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan) - if (selectedPlan) initEstimate(selectedPlan.planNo) + if (selectedPlan) initEstimate(selectedPlan?.planNo?? currentPid) }, [selectedPlan]) useEffect(() => { @@ -1281,7 +1281,7 @@ export default function Estimate({}) {
{getMessage('estimate.detail.objectNo')}
- {currentObjectNo} (Plan No: {planNo}) + {currentObjectNo} (Plan No: {currentPid})
diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index 5d2d1030..b876d517 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -241,7 +241,7 @@ export default function CanvasMenu(props) { return } setIsGlobalLoading(true) - promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => { + promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo??pid}/detail` }).then((res) => { if (res.status === 200) { const estimateDetail = res.data if (estimateDetail.estimateDate !== null) { @@ -249,7 +249,7 @@ export default function CanvasMenu(props) { setCurrentMenu(menu.title) setFloorPlanObjectNo({ floorPlanObjectNo: objectNo }) setIsGlobalLoading(false) - router.push(`/floor-plan/estimate/5?pid=${selectedPlan.planNo}&objectNo=${objectNo}`) + router.push(`/floor-plan/estimate/5?pid=${selectedPlan?.planNo??pid}&objectNo=${objectNo}`) if (pathname === '/floor-plan/estimate/5') { setIsGlobalLoading(false) } @@ -262,13 +262,13 @@ export default function CanvasMenu(props) { break case 'simulation': setIsGlobalLoading(true) - promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => { + promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo??pid}/detail` }).then((res) => { if (res.status === 200) { const estimateDetail = res.data if (estimateDetail.estimateDate !== null && estimateDetail.docNo) { setSelectedMenu(menu.type) setCurrentMenu(menu.title) - router.push(`/floor-plan/simulator/6?pid=${selectedPlan.planNo}&objectNo=${objectNo}`) + router.push(`/floor-plan/simulator/6?pid=${selectedPlan?.planNo??pid}&objectNo=${objectNo}`) if (pathname === '/floor-plan/simulator/6') { setIsGlobalLoading(false) } diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index ffb96d83..497d1307 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -12,10 +12,12 @@ import { import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions' import { isObjectNotEmpty } from '@/util/common-utils' +import Image from 'next/image' const Placement = forwardRef((props, refs) => { const { getMessage } = useMessage() const [useTab, setUseTab] = useState(true) + const [guideType, setGuideType] = useState('batch') const [isChidoriNotAble, setIsChidoriNotAble] = useState(false) @@ -317,60 +319,91 @@ const Placement = forwardRef((props, refs) => {
-
- {getMessage('modal.module.basic.setting.module.placement.max.size.check')} - -
-
-
-
- - - - - {selectedModules && - selectedModules.itemList?.map((item) => ( - // - ))} - {colspan > 1 && } - - - {selectedModules && - selectedModules.itemList?.map((item) => ( - <> - - {/* {colspan > 1 && } */} - - ))} - - - - {moduleSelectionData.roofConstructions.map((item, index) => ( - - - {moduleRowColArray[index]?.map((item, index2) => ( - <> - - {/* {colspan > 1 && } */} - {colspan > 1 && index2 === moduleRowColArray[index].length - 1 && } - - ))} - - ))} - -
- -
- - {item.itemNm} -
-
{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}
{getMessage('modal.module.basic.setting.module.placement.max.row')}{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}
-
- - {item.addRoof?.roofMatlNmJp} -
-
{item.moduleMaxRows}{item.mixModuleMaxRows}{item.maxRow}
+
+
+ {getMessage('modal.module.basic.setting.module.placement.info')} + +
+
+
+ +
+ {guideType === 'batch' && ( +
+
+ {getMessage('modal.module.basic.setting.module.placement.info.batch.content1')} +
+ {getMessage('modal.module.basic.setting.module.placement.info.batch.content2')} +
+
+
+ +
+
+ +
+
+
+ )} + {guideType === 'module' && ( +
+
+
+ + + + + {selectedModules && + selectedModules.itemList?.map((item) => ( + // + ))} + {colspan > 1 && } + + + {selectedModules && + selectedModules.itemList?.map((item) => ( + <> + + {/* {colspan > 1 && } */} + + ))} + + + + {moduleSelectionData.roofConstructions.map((item, index) => ( + + + {moduleRowColArray[index]?.map((item, index2) => ( + <> + + {/* {colspan > 1 && } */} + {colspan > 1 && index2 === moduleRowColArray[index].length - 1 && } + + ))} + + ))} + +
+ +
+ + {item.itemNm} +
+
{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}
{getMessage('modal.module.basic.setting.module.placement.max.row')}{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}
+
+ + {item.addRoof?.roofMatlNmJp} +
+
{item.moduleMaxRows}{item.mixModuleMaxRows}{item.maxRow}
+
+
+
+ )}
diff --git a/src/components/floor-plan/modal/basic/step/Trestle.jsx b/src/components/floor-plan/modal/basic/step/Trestle.jsx index 44a68236..070ce979 100644 --- a/src/components/floor-plan/modal/basic/step/Trestle.jsx +++ b/src/components/floor-plan/modal/basic/step/Trestle.jsx @@ -217,7 +217,7 @@ const Trestle = forwardRef((props, ref) => { stdWindSpeed: managementState?.standardWindSpeedId ?? '', stdSnowLd: managementState?.verticalSnowCover ?? '', inclCd: selectedRoof?.pitch ?? 0, - roofPitch: Math.round(selectedRoof?.roofPchBase ?? 0), + roofPitch: Math.round(hajebichi ?? 0), }, }) } @@ -236,9 +236,9 @@ const Trestle = forwardRef((props, ref) => { illuminationTp: managementState?.surfaceTypeValue ?? '', instHt: managementState?.installHeight ?? '', stdWindSpeed: managementState?.standardWindSpeedId ?? '', - stdSnowLd: +managementState?.verticalSnowCover ?? '', + stdSnowLd: managementState?.verticalSnowCover ?? '', inclCd: selectedRoof?.pitch ?? 0, - roofPitch: Math.round(selectedRoof?.roofPchBase ?? 0), + roofPitch: Math.round(hajebichi ?? 0), constTp: constructionList[index].constTp, snowGdPossYn: constructionList[index].snowGdPossYn, cvrYn: constructionList[index].cvrYn, @@ -304,6 +304,7 @@ const Trestle = forwardRef((props, ref) => { kerabaMargin, roofIndex: roof.index, raft: selectedRaftBase?.clCode, + hajebichi: hajebichi, trestle: { length: lengthBase, hajebichi: hajebichi, diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx index d268cc21..f4972c54 100644 --- a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -13,7 +13,7 @@ import { useRecoilState } from 'recoil' import { makersState, modelsState, modelState, pcsCheckState, selectedMakerState, selectedModelsState, seriesState } from '@/store/circuitTrestleAtom' import { POLYGON_TYPE } from '@/common/common' import { useSwal } from '@/hooks/useSwal' -import { canvasState } from '@/store/canvasAtom' +import { canvasState, canvasZoomState } from '@/store/canvasAtom' import { useTrestle } from '@/hooks/module/useTrestle' import { selectedModuleState } from '@/store/selectedModuleOptions' @@ -37,7 +37,7 @@ export default function CircuitTrestleSetting({ id }) { const { swalFire } = useSwal() const { saveEstimate } = useEstimate() const canvas = useRecoilValue(canvasState) - + const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const [tabNum, setTabNum] = useState(1) const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO) const [circuitAllocationType, setCircuitAllocationType] = useState(1) @@ -102,6 +102,13 @@ export default function CircuitTrestleSetting({ id }) { } }, []) + const handleZoomClear = () => { + setCanvasZoom(100) + canvas.set({ zoom: 1 }) + canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + canvas.renderAll() + } + // 수동할당 시 모듈 삭제 // 시리즈중 자동으로 추천 PCS 정보 조회 @@ -343,6 +350,7 @@ export default function CircuitTrestleSetting({ id }) { // 회로할당(승압설정) 저장 버튼 클릭 시 const onApply = async () => { + handleZoomClear() setAllModuleSurfaceIsComplete(false) setIsGlobalLoading(true) diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 0ac134ef..91332f43 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -227,7 +227,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla console.log('save Info', { ...basicSetting, selectedRoofMaterial: { - roofInfo, + ...newAddedRoofs[0], }, }) @@ -240,7 +240,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla * 선택된 지붕재 정보 */ selectedRoofMaterial: { - roofInfo, + ...newAddedRoofs[0], }, }) diff --git a/src/components/simulator/Simulator.jsx b/src/components/simulator/Simulator.jsx index 6683ff9e..94307e8d 100644 --- a/src/components/simulator/Simulator.jsx +++ b/src/components/simulator/Simulator.jsx @@ -114,7 +114,7 @@ export default function Simulator() { setHatsudenryouPeakcutAllSnow([]) if (objectNo && pid && selectedPlan) { - fetchObjectDetail(objectNo, selectedPlan.planNo) + fetchObjectDetail(objectNo, selectedPlan?.planNo??pid) fetchSimulatorNotice() setPwrGnrSimType('D') setPwrRecoil({ ...pwrRecoil, type: 'D' }) diff --git a/src/hooks/floorPlan/estimate/useEstimateController.js b/src/hooks/floorPlan/estimate/useEstimateController.js index 4a752a9f..9acb79f7 100644 --- a/src/hooks/floorPlan/estimate/useEstimateController.js +++ b/src/hooks/floorPlan/estimate/useEstimateController.js @@ -13,6 +13,8 @@ import { useSwal } from '@/hooks/useSwal' // Constants const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의 +import Config from '@/config/config.export' + // Helper functions const updateItemInList = (itemList, dispOrder, updates) => { return itemList.map((item) => (item.dispOrder === dispOrder ? { ...item, ...updates } : item)) @@ -464,11 +466,13 @@ export const useEstimateController = (planNo, flag) => { setIsGlobalLoading(true) await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }) - .then((res) => { + .then(async (res) => { setIsGlobalLoading(false) if (res.status === 201) { if (isObjectNotEmpty(res.data)) { let newObjectNo = res.data.objectNo + const copyImage = await handleEstimateImageCopy(params.objectNo, params.planNo, newObjectNo, '1') + swalFire({ text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'), type: 'alert', @@ -489,6 +493,27 @@ export const useEstimateController = (planNo, flag) => { }) } + const handleEstimateImageCopy = async (objectNo, planNo, newObjectNo, newPlanNo) => { + await promisePost({ url: `${Config().baseUrl}/api/image/estimate-image-copy`, data: { objectNo, planNo, newObjectNo, newPlanNo } }).then( + (res) => { + return res + }, + ) + } + + const handleDeleteEstimate = async (canvasStatus) => { + try { + setIsGlobalLoading(true) + await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/delete-estimate`, data: canvasStatus }).then((res) => { + if (res.status === 201) { + } + }) + } catch (e) { + console.error('error::::::::::::', e.response.data.message) + } + setIsGlobalLoading(false) + } + /** * 전각20자 (반각40자) */ @@ -509,5 +534,7 @@ export const useEstimateController = (planNo, flag) => { fetchSetting, handleEstimateFileDownload, handleEstimateCopy, + handleDeleteEstimate, + handleEstimateImageCopy, } } diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index a3f2eb0e..8f4a2a7e 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -661,7 +661,7 @@ export function useModuleBasicSetting(tabNum) { //가운데 가운데 if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) { - tempModule.top = holdCellCenterY - toFixedWithoutRounding(width / 2, 2) + tempModule.top = holdCellCenterY - toFixedWithoutRounding(height / 2, 2) } if (isChidori) { diff --git a/src/hooks/module/useModuleTrestle.js b/src/hooks/module/useModuleTrestle.js index 0143e92e..a39c8c1d 100644 --- a/src/hooks/module/useModuleTrestle.js +++ b/src/hooks/module/useModuleTrestle.js @@ -209,7 +209,7 @@ export function useModuleTrestle(props) { stdSnowLd: trestleState.stdSnowLd ?? '', inclCd: trestleState.inclCd ?? '', raftBaseCd: trestleState.raft ?? '', - roofPitch: Math.round(trestleState.roofPitch) ?? '', + roofPitch: trestleState.hajebichi ? trestleState.hajebichi : (trestleState.roofPitch ?? ''), }) .then((res) => { if (res?.data) setConstructionList(res.data) @@ -236,7 +236,7 @@ export function useModuleTrestle(props) { inclCd: trestleState.inclCd ?? '', constTp: trestleState.constTp ?? '', mixMatlNo: trestleState.mixMatlNo ?? '', - roofPitch: trestleState.roofPitch ?? '', + roofPitch: trestleState.hajebichi ? trestleState.hajebichi : (trestleState.roofPitch ?? ''), // workingWidth: trestleState.length ?? '', workingWidth: lengthBase ?? '', }, diff --git a/src/hooks/module/useTrestle.js b/src/hooks/module/useTrestle.js index 8a41149d..0aea8bfc 100644 --- a/src/hooks/module/useTrestle.js +++ b/src/hooks/module/useTrestle.js @@ -1627,6 +1627,8 @@ export const useTrestle = () => { // 랙 없음의 지지금구를 그린다. const drawBracketWithOutRack = (module, rackIntvlPct, count, l, direction, moduleIntvlHor, moduleIntvlVer) => { const { leftFindModuleList, rightFindModuleList, centerFindModuleList } = module + rackIntvlPct = rackIntvlPct === 0 ? 1 : rackIntvlPct // 0인 경우 1로 변경 + rackIntvlPct = 100 / rackIntvlPct // 퍼센트로 변경 let { width, height, left, top } = module let startPointX @@ -1641,14 +1643,14 @@ export const useTrestle = () => { break } else if (direction === 'east') { startPointX = left + width - startPointY = top + height - height / rackIntvlPct + startPointY = top + height - height / rackIntvlPct - 10 break } else if (direction === 'west') { startPointX = left startPointY = top + height / rackIntvlPct break } else if (direction === 'north') { - startPointX = left + width - width / rackIntvlPct + startPointX = left + width - width / rackIntvlPct - 10 startPointY = top break } @@ -1657,7 +1659,7 @@ export const useTrestle = () => { case 'R': { // 오른쪽부분 시작 점 if (direction === 'south') { - startPointX = left + width - width / rackIntvlPct + startPointX = left + width - width / rackIntvlPct - 10 startPointY = top + height / 2 + height / 2 break } else if (direction === 'east') { @@ -1666,7 +1668,7 @@ export const useTrestle = () => { break } else if (direction === 'west') { startPointX = left - startPointY = top + height - height / rackIntvlPct + startPointY = top + height - height / rackIntvlPct - 10 break } else if (direction === 'north') { startPointX = left + width / rackIntvlPct diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 1c7c0e39..db374c7f 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -498,11 +498,26 @@ export function useCanvasSetting(executeEffect = true) { roofSeq: 0, roofMatlCd: params.roofsData.roofMatlCd === null || params.roofsData.roofMatlCd === undefined ? 'ROOF_ID_WA_53A' : params.roofsData.roofMatlCd, - roofWidth: params.roofsData.roofWidth === null || params.roofsData.roofWidth === undefined ? 0 : params.roofsData.roofWidth, - roofHeight: params.roofsData.roofHeight === null || params.roofsData.roofHeight === undefined ? 0 : params.roofsData.roofHeight, + roofWidth: + params.selectedRoofMaterial.width === null || params.selectedRoofMaterial.width === undefined + ? !params.selectedRoofMaterial.widBase + ? 0 + : Number(params.roofsData.widBase) + : Number(params.selectedRoofMaterial.width), + roofHeight: + params.selectedRoofMaterial.height === null || params.selectedRoofMaterial.height === undefined + ? !params.selectedRoofMaterial.lenBase + ? 0 + : Number(params.selectedRoofMaterial.lenBase) + : Number(params.roofsData.roofHeight), roofHajebichi: - params.roofsData.roofHajebichi === null || params.roofsData.roofHajebichi === undefined ? 0 : params.roofsData.roofHajebichi, - roofGap: params.roofsData.roofGap === null || params.roofsData.roofGap === undefined ? 'HEI_455' : params.roofsData.roofGap, + params.selectedRoofMaterial.hajebichi === null || params.selectedRoofMaterial.hajebichi === undefined + ? 0 + : Number(params.selectedRoofMaterial.hajebichi), + roofGap: + params.selectedRoofMaterial.raft === null || params.selectedRoofMaterial.raft === undefined + ? params.selectedRoofMaterial.raftBaseCd + : params.roofsData.raft, roofLayout: params.roofsData.roofLayout === null || params.roofsData.roofLayout === undefined ? 'P' : params.roofsData.roofLayout, roofPitch: params.roofsData.roofPitch === null || params.roofsData.roofPitch === undefined ? 0 : params.roofsData.roofPitch, roofAngle: params.roofsData.roofAngle === null || params.roofsData.roofAngle === undefined ? 0 : params.roofsData.roofAngle, diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index fee893a6..5fbc3ab3 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -212,7 +212,6 @@ export function useRoofAllocationSetting(id) { } await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => { - swalFire({ text: getMessage(res.returnMessage) }) setIsGlobalLoading(false) }) diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 9f9e4ea0..8b7b687d 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -1,4 +1,4 @@ -import { useRef } from 'react' +import { useEffect, useRef } from 'react' import { useRecoilValue, useSetRecoilState } from 'recoil' import { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom' import { fabric } from 'fabric' @@ -15,7 +15,8 @@ import { useDotLineGrid } from '@/hooks/useDotLineGrid' import { useTempGrid } from '@/hooks/useTempGrid' import { gridColorState } from '@/store/gridAtom' import { gridDisplaySelector } from '@/store/settingAtom' -import { POLYGON_TYPE } from '@/common/common' +import { MENU, POLYGON_TYPE } from '@/common/common' +import useMenu from '@/hooks/common/useMenu' export function useEvent() { const canvas = useRecoilValue(canvasState) @@ -25,6 +26,7 @@ export function useEvent() { const setCanvasZoom = useSetRecoilState(canvasZoomState) const gridColor = useRecoilValue(gridColorState) const isGridDisplay = useRecoilValue(gridDisplaySelector) + const zoom = useRecoilValue(canvasZoomState) const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint() const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid() @@ -59,6 +61,13 @@ export function useEvent() { addDefaultEvent() } + useEffect(() => { + const whiteMenus = [MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH, MENU.BATCH_CANVAS.OBJECT_BATCH, MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING] + if (canvas && !whiteMenus.includes(currentMenu)) { + addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent) + } + }, [zoom]) + const addDefaultEvent = () => { //default Event 추가 addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent) @@ -280,7 +289,7 @@ export function useEvent() { } const drawMouseLine = (pointer) => { - const horizontalLine = new fabric.Line([-4 * canvas.width, pointer.y, 4 * canvas.width, pointer.y], { + const horizontalLine = new fabric.Line([-2 * canvas.width, pointer.y, 2 * canvas.width, pointer.y], { stroke: 'red', strokeWidth: 1, selectable: false, @@ -288,7 +297,7 @@ export function useEvent() { }) // 세로선 - const verticalLine = new fabric.Line([pointer.x, -4 * canvas.height, pointer.x, 4 * canvas.height], { + const verticalLine = new fabric.Line([pointer.x, -2 * canvas.height, pointer.x, 2 * canvas.height], { stroke: 'red', strokeWidth: 1, selectable: false, diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index fcf14312..9d1a48eb 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -159,5 +159,6 @@ export const useLine = () => { addPitchText, removePitchText, addPitchTextsByOuterLines, + getLengthByLine, } } diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index d8e1c9d6..52c60f80 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -30,6 +30,7 @@ import { useCanvasPopupStatusController } from './common/useCanvasPopupStatusCon import { useCanvasMenu } from './common/useCanvasMenu' import { QcastContext } from '@/app/QcastProvider' import { unescapeString } from '@/util/common-utils' +import { useTrestle } from '@/hooks/module/useTrestle' /** * 플랜 처리 훅 @@ -54,7 +55,7 @@ export function usePlan(params = {}) { const { getMessage } = useMessage() const { get, post, promisePost, promisePut, promiseDel, promiseGet } = useAxios() - const { setEstimateContextState } = useEstimateController() + const { setEstimateContextState, handleDeleteEstimate, handleEstimateImageCopy } = useEstimateController() const resetOuterLinePoints = useResetRecoilState(outerLinePointsState) const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState) @@ -78,6 +79,7 @@ export function usePlan(params = {}) { const resetCurrentObject = useResetRecoilState(currentObjectState) //선택된 모듈 배치면 초기화 const resetModuleSetupSurface = useResetRecoilState(moduleSetupSurfaceState) + const { isAllComplete } = useTrestle() /** * 마우스 포인터의 가이드라인 제거 @@ -172,7 +174,11 @@ export function usePlan(params = {}) { */ const saveCanvas = async (saveAlert = true) => { const canvasStatus = currentCanvasData('save') - await putCanvasStatus(canvasStatus, saveAlert) + const result = await putCanvasStatus(canvasStatus, saveAlert) + //캔버스 저장 완료 후 + if (result && !isAllComplete()) { + handleDeleteEstimate(currentCanvasPlan) + } } /** @@ -301,6 +307,9 @@ export function usePlan(params = {}) { setModuleSelectionDataStore(copyData) if (copyData.module) setSelectedModules(copyData.module) setSelectedMenu(currentSelectedMenu) + + //이미지 복사 + handleEstimateImageCopy(planData.objectNo, planData.planNo, planData.objectNo, newPlan.planNo) } else { setSelectedMenu('placement') } @@ -318,20 +327,24 @@ export function usePlan(params = {}) { * @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), } + await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData }) .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) => { swalFire({ text: error.message, icon: 'error' }) }) + return rtn } /** @@ -602,8 +615,10 @@ export function usePlan(params = {}) { 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((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) } }) } diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index bcaec973..b83d4ac4 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -15,6 +15,7 @@ import { flowDisplaySelector } from '@/store/settingAtom' import { fontSelector } from '@/store/fontAtom' import { QLine } from '@/components/fabric/QLine' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' +import { useLine } from '@/hooks/useLine' export const usePolygon = () => { const canvas = useRecoilValue(canvasState) @@ -24,6 +25,8 @@ export const usePolygon = () => { const currentAngleType = useRecoilValue(currentAngleTypeSelector) const pitchText = useRecoilValue(pitchTextSelector) + const { getLengthByLine } = useLine() + const addPolygon = (points, options, isAddCanvas = true) => { const polygon = new QPolygon(points, { ...options, @@ -1093,25 +1096,37 @@ export const usePolygon = () => { }) if (startFlag && endFlag) { - if (!representLines.includes(line)) { + if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { representLines.push(line) } } }) - // blue로 생성된 것들은 대표라인이 될 수 없음. - representLines = representLines.filter((line) => line.stroke !== 'blue') // representLines중 가장 긴 line을 찾는다. representLines.forEach((line) => { if (!representLine) { representLine = line } else { - if (representLine.length < line.length) { + if (getLengthByLine(representLine) < getLengthByLine(line)) { representLine = line } } }) - const direction = polygon.direction ?? representLine?.direction + + if (!representLine) { + representLines = representLines.filter((line) => line.stroke !== 'blue') + representLines.forEach((line) => { + if (!representLine) { + representLine = line + } else { + if (representLine.length < line.length) { + representLine = line + } + } + }) + } + + const direction = polygon.direction ?? representLine?.direction ?? '' const polygonDirection = polygon.direction switch (direction) { @@ -1178,7 +1193,7 @@ export const usePolygon = () => { }) canvas.add(roof) - // addLengthText(roof) + addLengthText(roof) canvas.remove(polygon) canvas.renderAll() }) diff --git a/src/locales/ja.json b/src/locales/ja.json index 0882c1d7..3b6962c6 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -666,9 +666,9 @@ "join.sub1.title": "販売代理店情報", "join.sub1.comment": "※登録される販売店の会社名を入力してください。 (2次店は「○○販売株式会社(2次店:××設備株式会社)」でご記入ください。)", "join.sub1.storeQcastNm": "販売代理店名", - "join.sub1.storeQcastNm_placeholder": "株式会社エネルギーギアソリューションアンサービス(2次点:山口周期販売有限会社)", + "join.sub1.storeQcastNm_placeholder": "ハンファジャパン株式会社", "join.sub1.storeQcastNmKana": "販売代理店名フリガナ", - "join.sub1.storeQcastNmKana_placeholder": "株式会社エネルギーギアソリューション", + "join.sub1.storeQcastNmKana_placeholder": "ハンファジャパンカブシキカイシャ", "join.sub1.postCd": "郵便番号", "join.sub1.postCd_placeholder": "数字7桁", "join.sub1.addr": "住所", @@ -681,7 +681,7 @@ "join.sub2.title": "担当者情報", "join.sub2.userNm": "担当者名", "join.sub2.userNmKana": "担当者名ふりがな", - "join.sub2.userId": "申請ID", + "join.sub2.userId": "ユーザーID", "join.sub2.email": "メールアドレス", "join.sub2.telNo": "電話番号", "join.sub2.telNo_placeholder": "00 0000 0000", @@ -689,12 +689,12 @@ "join.sub2.fax_placeholder": "00 0000 0000", "join.sub2.category": "部署名", "join.btn.login_page": "ログイン画面に移動", - "join.btn.approval_request": "ID承認要求", + "join.btn.approval_request": "ID申請", "join.complete.title": "HANASYS設計ログインID発行申請完了", "join.complete.contents": "※申請したIDが承認されると、担当者情報に入力したEメールアドレスにログイン関連案内メールが送信されます。", "join.complete.email_comment": "担当者のメールアドレス", "join.validation.check1": "{0}形式を確認してください。", - "join.complete.save.confirm": "ハンファジャパンの担当者にID承認が要求された場合、これ以上情報を修正することはできません。申請しますか?", + "join.complete.save.confirm": "ID申請を完了後は申請情報の修正が出来ません。申請しますか?", "stuff.gridHeader.lastEditDatetime": "更新日時", "stuff.gridHeader.objectNo": "物件番号", "stuff.gridHeader.planTotCnt": "プラン数", @@ -1105,6 +1105,11 @@ "module.layout.setup.max.count": "モジュールの単体での最大段数は{0}、最大列数は{1}です。 (JA)", "module.layout.setup.max.count.multiple": "モジュール{0}の単体での最大段数は{1}、最大列数は{2}です。 (JA)", "roofAllocation.not.found": "割り当てる屋根がありません。 (JA)", + "modal.module.basic.setting.module.placement.info": "モジュール配置案内", + "modal.module.basic.setting.module.placement.info.batch": "千鳥配置を手動で行う際の注意事項", + "modal.module.basic.setting.module.placement.info.batch.content1": "千鳥配置する時に図のような配置ができてしまいますが、正常な積算ができません。", + "modal.module.basic.setting.module.placement.info.batch.content2": "千鳥で配置する時は、千鳥配置を「する」にして、モジュールが吸着されるようにして下さい。", + "modal.module.basic.setting.module.placement.info.module": "屋根材別 単一・混合モジュールの最大段数", "modal.module.basic.setting.module.placement.max.size.check": "屋根材別モジュールの単体の単体での最大段数、2種混合の段数を確認して下さい", "modal.module.basic.setting.module.placement.max.row": "単体で\rの最大段数", "modal.module.basic.setting.module.placement.max.rows.multiple": "2種混合時\rの最大段数", diff --git a/src/locales/ko.json b/src/locales/ko.json index 67176ee8..f0169fbb 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -666,9 +666,9 @@ "join.sub1.title": "판매대리점 정보", "join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)", "join.sub1.storeQcastNm": "판매대리점명", - "join.sub1.storeQcastNm_placeholder": "주식회사 에너지 기어 솔루션 앤 서비스 (2차점: 야마구치 주기 판매 유한회사)", + "join.sub1.storeQcastNm_placeholder": "한화재팬 주식회사", "join.sub1.storeQcastNmKana": "판매대리점명 후리가나", - "join.sub1.storeQcastNmKana_placeholder": "주식회사 에너지 기어 솔루션", + "join.sub1.storeQcastNmKana_placeholder": "한화재팬 카부시키 카이샤", "join.sub1.postCd": "우편번호", "join.sub1.postCd_placeholder": "숫자 7자리", "join.sub1.addr": "주소", @@ -681,7 +681,7 @@ "join.sub2.title": "담당자 정보", "join.sub2.userNm": "담당자명", "join.sub2.userNmKana": "담당자명 후리가나", - "join.sub2.userId": "신청 ID", + "join.sub2.userId": "사용자 ID", "join.sub2.email": "이메일 주소", "join.sub2.telNo": "전화번호", "join.sub2.telNo_placeholder": "00 0000 0000", @@ -1105,6 +1105,11 @@ "module.layout.setup.max.count": "모듈의 최대 단수는 {0}, 최대 열수는 {1} 입니다.", "module.layout.setup.max.count.multiple": "모듈 {0}번의 최대 단수는 {1}, 최대 열수는 {2} 입니다.", "roofAllocation.not.found": "할당할 지붕이 없습니다.", + "modal.module.basic.setting.module.placement.info": "모듈 배치 안내", + "modal.module.basic.setting.module.placement.info.batch": "치도리 수동 배치 시 유의사항", + "modal.module.basic.setting.module.placement.info.batch.content1": "치조 배치할 때 그림과 같은 배치가 되어 버립니다만, 정상적인 적산을 할 수 없습니다.", + "modal.module.basic.setting.module.placement.info.batch.content2": "치조로 배치할 때는, 치조 배치를 「한다」로 하고, 모듈이 흡착되도록 해 주세요.", + "modal.module.basic.setting.module.placement.info.module": "지붕재별 단일·혼합 모듈 최대 단수", "modal.module.basic.setting.module.placement.max.size.check": "지붕재별 모듈 단체의 최대 단수, 2종 혼합 단수를 확인하십시오.", "modal.module.basic.setting.module.placement.max.row": "최대 단수", "modal.module.basic.setting.module.placement.max.rows.multiple": "2종 혼합 최대단수", diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index 30c18715..c90372bf 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -2457,3 +2457,22 @@ $alert-color: #101010; } } } + +// 2025-05-30 지붕 모듈 +.roof-warning-img-wrap{ + display: flex; + align-items: center; + gap: 15px; + justify-content: center; +} + +.roof-content-tab-wrap{ + display: flex; + padding-top: 10px; +} +.hide-tab-contents{ + &.hide{ + height: 0; + overflow: hidden; + } +} \ No newline at end of file