Merge branch 'dev' into feature/test-jy

# Conflicts:
#	src/hooks/roofcover/usePropertiesSetting.js
#	src/hooks/roofcover/useRoofAllocationSetting.js
This commit is contained in:
Jaeyoung Lee 2024-11-05 14:47:04 +09:00
commit 57ee521410
44 changed files with 4344 additions and 3427 deletions

View File

@ -0,0 +1,3 @@
<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L5.5 5.5L10 1" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 210 B

View File

@ -0,0 +1,3 @@
<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Vector 7174" d="M1 1L5.5 5.5L10 1" stroke="#C2D0DD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B

View File

@ -1,128 +0,0 @@
'use client'
import { Select, SelectItem } from '@nextui-org/react'
import { useEffect, useState } from 'react'
import { useAxios } from '@/hooks/useAxios'
export default function RoofSelect() {
const [roofMaterials, setRoofMaterials] = useState([])
const [manufacturers, setManufacturers] = useState([])
const [trestles, setTrestles] = useState([])
const [modules, setModules] = useState([])
const [originTrestles, setOriginTrestles] = useState([])
const [roofMaterialId, setRoofMaterialId] = useState(null)
const [manufacturerId, setManufacturerId] = useState(null)
const [trestleId, setTrestleId] = useState(null)
const { get } = useAxios()
useEffect(() => {
get({ url: '/api/roof-material/roof-material-infos' }).then((res) => {
//TODO: error handling
if (!res) return
setRoofMaterials(res)
})
}, [])
useEffect(() => {
if (!roofMaterialId) {
return
}
get({ url: `/api/roof-material/roof-material-infos/${roofMaterialId}/trestles` }).then((res) => {
if (res.length === 0) {
return
}
setOriginTrestles(res)
const manufactural = res.map((trestle) => {
return { id: trestle.manufacturerId, name: trestle.manufacturerName }
})
// Remove duplicates
const uniqueManufactural = Array.from(new Set(manufactural.map((a) => a.id))).map((id) => {
return manufactural.find((a) => a.id === id)
})
setManufacturers(uniqueManufactural)
})
}, [roofMaterialId])
useEffect(() => {
if (!manufacturerId) {
return
}
const trestles = originTrestles.filter((trestle) => trestle.manufacturerId === manufacturerId)
setTrestles(trestles)
}, [manufacturerId])
useEffect(() => {
if (!trestleId) {
return
}
get({ url: `/api/module/module-infos?roofMaterialId=${roofMaterialId}&trestleId=${trestleId}` }).then((res) => {
if (res.length === 0) {
return
}
setModules(res)
})
}, [trestleId])
const handleRoofMaterialOnChange = (e) => {
const roofMaterialId = e.target.value
setRoofMaterialId(roofMaterialId)
setManufacturers([])
setManufacturerId(null)
setTrestleId(null)
setTrestles([])
setModules([])
}
const handleManufacturersOnChange = (e) => {
const manufacturerId = Number(e.target.value)
setTrestles([])
setManufacturerId(manufacturerId)
setTrestleId(null)
setModules([])
}
const handleTrestlesOnChange = (e) => {
const trestleId = Number(e.target.value)
setTrestleId(trestleId)
setModules([])
}
return (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
{roofMaterials.length > 0 && (
<Select label="지붕재" className="max-w-xs" onChange={handleRoofMaterialOnChange}>
{roofMaterials.map((roofMaterial) => (
<SelectItem key={roofMaterial.id}>{roofMaterial.name}</SelectItem>
))}
</Select>
)}
{manufacturers.length > 0 && (
<Select label="제조 회사" className="max-w-xs" onChange={handleManufacturersOnChange}>
{manufacturers.map((manufacturer) => (
<SelectItem key={manufacturer.id}>{manufacturer.name}</SelectItem>
))}
</Select>
)}
{trestles.length > 0 && (
<Select label="가대" className="max-w-xs" onChange={handleTrestlesOnChange}>
{trestles.map((trestle) => (
<SelectItem key={trestle.id}>{trestle.name}</SelectItem>
))}
</Select>
)}
{modules.length > 0 && (
<Select label="설치가능 모듈" className="max-w-xs">
{modules.map((module) => (
<SelectItem key={module.id}>{module.name}</SelectItem>
))}
</Select>
)}
</div>
)
}

View File

@ -1,14 +1,8 @@
import Roof2 from '@/components/Roof2' import Roof2 from '@/components/Roof2'
import RoofSelect from '@/app/roof2/RoofSelect'
export default async function Roof2Page() { export default async function Roof2Page() {
return ( return (
<> <>
<div>
<div className="m-2">
<RoofSelect />
</div>
</div>
<div className="flex flex-col justify-center my-8 pt-20"> <div className="flex flex-col justify-center my-8 pt-20">
<Roof2 /> <Roof2 />
</div> </div>

View File

@ -29,13 +29,6 @@ export default function InitSettingsModal(props) {
//const { get, post } = useAxios() //const { get, post } = useAxios()
useEffect(() => { useEffect(() => {
get({ url: '/api/roof-material/roof-material-infos' }).then((res) => {
//TODO: error handling
if (!res) return
setRoofMaterials(res)
})
get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${objectNo}` }).then((res) => { get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${objectNo}` }).then((res) => {
if (res.length == 0) return if (res.length == 0) return

View File

@ -8,7 +8,7 @@ import { contextPopupPositionState } from '@/store/popupAtom'
export default function ColorPickerModal(props) { export default function ColorPickerModal(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState) // const contextPopupPosition = useRecoilValue(contextPopupPositionState) //
const { isShow, setIsShow, pos = contextPopupPosition, color = '#ff0000', setColor, id } = props const { isShow, setIsShow, pos = contextPopupPosition, color = '#ff0000', setColor, id, isConfig = false } = props
const { getMessage } = useMessage() const { getMessage } = useMessage()
const [originColor, setOriginColor] = useState(color) const [originColor, setOriginColor] = useState(color)
const { closePopup } = usePopup() const { closePopup } = usePopup()
@ -29,7 +29,7 @@ export default function ColorPickerModal(props) {
setIsShow(false) setIsShow(false)
} }
console.log(id) console.log(id)
closePopup(id) closePopup(id, isConfig)
}} }}
> >
닫기 닫기
@ -49,7 +49,7 @@ export default function ColorPickerModal(props) {
if (setColor) setColor(originColor) if (setColor) setColor(originColor)
if (setIsShow) setIsShow(false) if (setIsShow) setIsShow(false)
closePopup(id) closePopup(id, isConfig)
}} }}
> >
{getMessage('common.message.save')} {getMessage('common.message.save')}

View File

@ -3,12 +3,14 @@ import { useEffect } from 'react'
import '@/styles/contents.scss' import '@/styles/contents.scss'
import { useRecoilState } from 'recoil' import { useRecoilState } from 'recoil'
import { contextMenuListState, contextMenuState } from '@/store/contextMenu' import { contextMenuListState, contextMenuState } from '@/store/contextMenu'
import { useTempGrid } from '@/hooks/useTempGrid'
export default function QContextMenu(props) { export default function QContextMenu(props) {
const { contextRef, canvasProps, handleKeyup } = props const { contextRef, canvasProps, handleKeyup } = props
const [contextMenu, setContextMenu] = useRecoilState(contextMenuState) const [contextMenu, setContextMenu] = useRecoilState(contextMenuState)
const [contextMenuList, setContextMenuList] = useRecoilState(contextMenuListState) const [contextMenuList, setContextMenuList] = useRecoilState(contextMenuListState)
const activeObject = canvasProps?.getActiveObject() // const activeObject = canvasProps?.getActiveObject() //
const { tempGridMode, setTempGridMode } = useTempGrid()
let contextType = '' let contextType = ''
@ -32,6 +34,7 @@ export default function QContextMenu(props) {
const handleContextMenu = (e) => { const handleContextMenu = (e) => {
// e.preventDefault() // contextmenu // e.preventDefault() // contextmenu
if (tempGridMode) return
const position = { const position = {
x: window.innerWidth / 2 < e.pageX ? e.pageX - 240 : e.pageX, x: window.innerWidth / 2 < e.pageX ? e.pageX - 240 : e.pageX,
y: window.innerHeight / 2 < e.pageY ? getYPosition(e) : e.pageY, y: window.innerHeight / 2 < e.pageY ? getYPosition(e) : e.pageY,

View File

@ -14,15 +14,7 @@ const fonts = [
{ name: '@Yu Gothic UI', value: '@Yu Gothic UI' }, { name: '@Yu Gothic UI', value: '@Yu Gothic UI' },
{ name: 'Yu Gothic UI', value: 'Yu Gothic UI' }, { name: 'Yu Gothic UI', value: 'Yu Gothic UI' },
] ]
const fontOptions = [
{ name: '보통', value: 'normal' },
{ name: '기울임꼴', value: 'italic' },
{
name: '굵게',
value: 'bold',
},
{ name: '굵은 기울임꼴', value: 'boldAndItalic' },
]
const fontSizes = [ const fontSizes = [
...Array.from({ length: 4 }).map((_, index) => { ...Array.from({ length: 4 }).map((_, index) => {
return { name: index + 8, value: index + 8 } return { name: index + 8, value: index + 8 }
@ -34,20 +26,10 @@ const fontSizes = [
{ name: 48, value: 48 }, { name: 48, value: 48 },
{ name: 72, value: 72 }, { name: 72, value: 72 },
] ]
const fontColors = [
{ name: '검정색', value: 'black' },
{ name: '빨강색', value: 'red' },
{ name: '파랑색', value: 'blue' },
{ name: '회색', value: 'gray' },
{ name: '황색', value: 'yellow' },
{ name: '녹색', value: 'green' },
{ name: '분홍색', value: 'pink' },
{ name: '황금색', value: 'gold' },
{ name: '남색', value: 'darkblue' },
]
export default function FontSetting(props) { export default function FontSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState) const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, setIsShow, pos = contextPopupPosition, type } = props const { id, setIsShow, pos = contextPopupPosition, type, isConfig = false } = props
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { closePopup } = usePopup() const { closePopup } = usePopup()
const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom)
@ -57,7 +39,26 @@ export default function FontSetting(props) {
const [selectedFontWeight, setSelectedFontWeight] = useState(currentFont.fontWeight) const [selectedFontWeight, setSelectedFontWeight] = useState(currentFont.fontWeight)
const [selectedFontSize, setSelectedFontSize] = useState(currentFont.fontSize) const [selectedFontSize, setSelectedFontSize] = useState(currentFont.fontSize)
const [selectedFontColor, setSelectedFontColor] = useState(currentFont.fontColor) const [selectedFontColor, setSelectedFontColor] = useState(currentFont.fontColor)
const fontOptions = [
{ name: getMessage('font.style.normal'), value: 'normal' },
{ name: getMessage('font.style.italic'), value: 'italic' },
{
name: getMessage('font.style.bold'),
value: 'bold',
},
{ name: getMessage('font.style.bold.italic'), value: 'boldAndItalic' },
]
const fontColors = [
{ name: getMessage('color.black'), value: 'black' },
{ name: getMessage('color.red'), value: 'red' },
{ name: getMessage('color.blue'), value: 'blue' },
{ name: getMessage('color.gray'), value: 'gray' },
{ name: getMessage('color.yellow'), value: 'yellow' },
{ name: getMessage('color.green'), value: 'green' },
{ name: getMessage('color.pink'), value: 'pink' },
{ name: getMessage('color.gold'), value: 'gold' },
{ name: getMessage('color.darkblue'), value: 'darkblue' },
]
const handleSaveBtn = () => { const handleSaveBtn = () => {
setGlobalFont((prev) => { setGlobalFont((prev) => {
return { return {
@ -83,7 +84,7 @@ export default function FontSetting(props) {
className="modal-close" className="modal-close"
onClick={() => { onClick={() => {
if (setIsShow) setIsShow(false) if (setIsShow) setIsShow(false)
closePopup(id) closePopup(id, isConfig)
}} }}
> >
닫기 닫기

View File

@ -5,5 +5,9 @@ import { Fragment } from 'react'
export default function PopupManager() { export default function PopupManager() {
const [popup, setPopup] = useRecoilState(popupState) const [popup, setPopup] = useRecoilState(popupState)
return popup.children?.map((child) => <Fragment key={child.id}>{child.component}</Fragment>)
return [
...popup?.config.map((child) => <Fragment key={child.id}>{child.component}</Fragment>),
...popup?.other.map((child) => <Fragment key={child.id}>{child.component}</Fragment>),
]
} }

View File

@ -8,13 +8,41 @@ import { useMessage } from '@/hooks/useMessage'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import SingleDatePicker from '../common/datepicker/SingleDatePicker' import SingleDatePicker from '../common/datepicker/SingleDatePicker'
import EstimateFileUploader from './EstimateFileUploader' import EstimateFileUploader from './EstimateFileUploader'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { isObjectNotEmpty } from '@/util/common-utils'
import dayjs from 'dayjs'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import Select from 'react-select'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
export default function Estimate({ params }) { export default function Estimate({ params }) {
const [objectNo, setObjectNo] = useState('') const [objectNo, setObjectNo] = useState('') //
const [files, setFiles] = useState([]) // const [planNo, setPlanNo] = useState('') //
const [files, setFiles] = useState([]) //
//
const [hidden, setHidden] = useState(false)
//
const { findCommonCode } = useCommonCode()
const [honorificCodeList, setHonorificCodeList] = useState([]) //
const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = {
startDate,
setStartDate,
}
const sessionState = useRecoilValue(sessionStore) const sessionState = useRecoilValue(sessionStore)
const objectRecoil = useRecoilValue(floorPlanObjectState) const objectRecoil = useRecoilValue(floorPlanObjectState)
//
const { state, setState } = useEstimateController(params.pid)
const globalLocaleState = useRecoilValue(globalLocaleStore)
const { get, post } = useAxios(globalLocaleState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { setMenuNumber } = useCanvasMenu() const { setMenuNumber } = useCanvasMenu()
@ -27,46 +55,54 @@ export default function Estimate({ params }) {
setUploadFiles: setFiles, setUploadFiles: setFiles,
} }
useEffect(() => {
setObjectNo(objectRecoil.floorPlanObjectNo)
}, [objectRecoil])
useEffect(() => {
if (objectNo) {
//Q101X278191023001
console.log('세션정보::::', sessionState)
//API
}
}, [objectNo])
useEffect(() => { useEffect(() => {
setMenuNumber(5) setMenuNumber(5)
setObjectNo(objectRecoil.floorPlanObjectNo)
setPlanNo(params.pid)
//
const code1 = findCommonCode(200800)
if (code1 != null) {
setHonorificCodeList(code1)
}
// API
//http://localhost:8080/api/estimate/special-note-list
}, []) }, [])
// set
useEffect(() => {
let estimateDatej = dayjs(startDate).format('YYYY-MM-DD')
setState({ estimateDate: estimateDatej })
}, [startDate])
return ( return (
<div className="sub-content estimate"> <div className="sub-content estimate">
<div className="sub-content-inner"> <div className="sub-content-inner">
{/* 물건번호, 견적서번호, 등록일, 변경일시 시작 */} {/* 물건번호, 견적서번호, 등록일, 변경일시 시작 */}
{/* <form onSubmit={handleSubmit(onValid)}> */}
<div className="sub-content-box"> <div className="sub-content-box">
<div className="sub-table-box"> <div className="sub-table-box">
<div className="estimate-list-wrap one"> <div className="estimate-list-wrap one">
<div className="estimate-box"> <div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div> <div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div>
<div className="estimate-name"> <div className="estimate-name">
{objectNo} (Plan No: {params.pid}) {objectNo} (Plan No: {planNo})
</div> </div>
</div> </div>
<div className="estimate-box"> <div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.estimateNo')}</div> <div className="estimate-tit">{getMessage('estimate.detail.docNo')}</div>
<div className="estimate-name">5242310200065242</div> <div className="estimate-name">{state.docNo}</div>
</div> </div>
<div className="estimate-box"> <div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.createDatetime')}</div> <div className="estimate-tit">{getMessage('estimate.detail.drawingEstimateCreateDate')}</div>
<div className="estimate-name">9999.09.28</div> <div className="estimate-name">
{state?.drawingEstimateCreateDate ? `${dayjs(state.drawingEstimateCreateDate).format('YYYY.MM.DD')}` : ''}
</div>
</div> </div>
<div className="estimate-box"> <div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.lastEditDatetime')}</div> <div className="estimate-tit">{getMessage('estimate.detail.lastEditDatetime')}</div>
<div className="estimate-name">9999.09.28 06:36</div> <div className="estimate-name">{state?.lastEditDatetime ? `${dayjs(state.lastEditDatetime).format('YYYY.MM.DD HH:mm')}` : ''}</div>
</div> </div>
</div> </div>
</div> </div>
@ -99,7 +135,7 @@ export default function Estimate({ params }) {
</th> </th>
<td> <td>
<div className="date-picker" style={{ width: '350px' }}> <div className="date-picker" style={{ width: '350px' }}>
<SingleDatePicker /> <SingleDatePicker {...singleDatePickerProps} />
</div> </div>
</td> </td>
</tr> </tr>
@ -113,64 +149,152 @@ export default function Estimate({ params }) {
</th> </th>
<td> <td>
<div className="input-wrap" style={{ width: '350px' }}> <div className="input-wrap" style={{ width: '350px' }}>
<input type="text" className="input-light" defaultValue={'물건정보에서 입력한 담당자명 표시'} /> <input
type="text"
className="input-light"
defaultValue={state?.charger}
onChange={(e) => {
// charger
// console.log(':::::', e.target.value)
setState({ charger: e.target.value })
}}
/>
</div> </div>
</td> </td>
</tr> </tr>
<tr> <tr>
{/* 안건명 */} {/* 안건명 */}
<th> <th>
{getMessage('estimate.detail.title')} <span className="important">*</span> {getMessage('estimate.detail.objectName')} <span className="important">*</span>
</th> </th>
<td colSpan={3}> <td colSpan={3}>
<div className="form-flex-wrap"> <div className="form-flex-wrap">
<div className="input-wrap mr5" style={{ width: '610px' }}> <div className="input-wrap mr5" style={{ width: '610px' }}>
<input type="text" className="input-light" defaultValue={'안건명:::'} /> <input
type="text"
className="input-light"
defaultValue={state?.objectName}
onChange={(e) => {
// objectName
// console.log('::::', e.target.value)
setState({ objectName: e.target.value })
}}
/>
</div> </div>
<div className="input-wrap" style={{ width: '200px' }}> <div className="select-wrap" style={{ width: '200px' }}>
<input type="text" className="input-light" defaultValue={'경칭?'} /> <Select
id="objectNameOmit"
instanceId="objectNameOmit"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={honorificCodeList}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
setState({ objectNameOmit: e.clCodeNm })
} else {
// console.log('XXX')
setState({ objectNameOmit: '' })
}
}}
getOptionLabel={(x) => x.clCodeNm}
getOptionValue={(x) => x.clCode}
isClearable={true}
isSearchable={false}
value={honorificCodeList.filter(function (option) {
return option.clCodeNm === state.objectNameOmit
})}
/>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
<tr> <tr>
{/* 메모 */} {/* 물건정보에서 입력한 메모 */}
<th>{getMessage('estimate.detail.remarks')}</th> <th>{getMessage('estimate.detail.objectRemarks')}</th>
<td colSpan={3}>물건정보에서 입력한 메모 표시</td> <td colSpan={3}>{state?.objectRemarks}</td>
</tr> </tr>
<tr> <tr>
{/* 주문분류 */} {/* 주문분류 */}
<th> <th>
{getMessage('estimate.detail.orderType')} <span className="important">*</span> {getMessage('estimate.detail.estimateType')} <span className="important">*</span>
</th> </th>
<td colSpan={3}> <td colSpan={3}>
<div className="radio-wrap"></div> <div className="radio-wrap">
<div className="d-check-radio light mr10">
<input
type="radio"
name="estimateType"
id="YJSS"
value={'YJSS'}
checked={state?.estimateType === 'YJSS' ? true : false}
onChange={(e) => {
setState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJSS">住宅PKG</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="estimateType"
id="YJOD"
value={'YJOD'}
checked={state?.estimateType === 'YJOD' ? true : false}
onChange={(e) => {
setState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJOD">積上げ( YJOD )</label>
</div>
</div>
</td> </td>
</tr> </tr>
<tr> <tr>
{/* 지붕재・사양시공 최대4개*/} {/* 지붕재・사양시공 최대4개*/}
<th>{getMessage('estimate.detail.roofCns')}</th> <th>{getMessage('estimate.detail.roofCns')}</th>
<td colSpan={3}> <td colSpan={3}>
<div className="form-flex-wrap mb5"> {state?.roofMaterialIdMulti?.split('、').map((row, index) => {
//
let roofList = row
let roofListLength = state?.roofMaterialIdMulti?.split('、').length
let style = 'mb5'
if (roofListLength == index + 1) {
style = ''
}
//
let constructSpecificationMulti = state?.constructSpecificationMulti?.split('、')
return (
<>
<div className={`form-flex-wrap ${style}`}>
<div className="input-wrap mr5" style={{ width: '610px' }}> <div className="input-wrap mr5" style={{ width: '610px' }}>
<input type="text" className="input-light" defaultValue={'ハゼ式折板(角ハゼ)'} readOnly /> <input type="text" className="input-light" defaultValue={roofList} readOnly />
</div> </div>
<div className="input-wrap" style={{ width: '200px' }}> <div className="input-wrap" style={{ width: '200px' }}>
<input type="text" className="input-light" defaultValue={'標準施工'} readOnly /> <input type="text" className="input-light" defaultValue={constructSpecificationMulti[index]} readOnly />
</div> </div>
</div> </div>
<div className="form-flex-wrap mb5"></div> </>
<div className="form-flex-wrap mb5"></div> )
{/* 마지막div엔 mb5 제외 */} })}
<div className="form-flex-wrap"></div>
</td> </td>
</tr> </tr>
<tr> <tr>
{/* 비고 */} {/* 비고 */}
<th>{getMessage('estimate.detail.note')}</th> <th>{getMessage('estimate.detail.remarks')}</th>
<td colSpan={3}> <td colSpan={3}>
<div className="input-wrap"> <div className="input-wrap">
<input type="text" className="input-light" /> <input
type="text"
className="input-light"
defaultValue={state?.remarks}
onChange={(e) => {
//
// console.log(':::::', e.target.value)
setState({ remarks: e.target.value })
}}
/>
</div> </div>
</td> </td>
</tr> </tr>
@ -200,25 +324,6 @@ export default function Estimate({ params }) {
<th>{getMessage('estimate.detail.header.fileList1')}</th> <th>{getMessage('estimate.detail.header.fileList1')}</th>
<td> <td>
<EstimateFileUploader {...fileUploadProps} /> <EstimateFileUploader {...fileUploadProps} />
{/* <div className="drag-file-box">
<div className="btn-area">
<Button type="button" className="btn-origin grey" onClick={handleButtonClick}>
{getMessage('estimate.detail.fileList.btn')}
</Button>
<input
type="file"
id="fileUpload"
name="fileUpload"
ref={fileInputRef}
onChange={onChangeFiles}
style={{ display: 'none' }}
/>
</div>
<div className="drag-file-area">
<p>Drag file here</p>
<ul className="file-list"></ul>
</div>
</div> */}
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -246,21 +351,40 @@ export default function Estimate({ params }) {
<h3 className="product">{getMessage('estimate.detail.header.specialEstimate')}</h3> <h3 className="product">{getMessage('estimate.detail.header.specialEstimate')}</h3>
<div className="product_tit">{getMessage('estimate.detail.header.specialEstimateProductInfo')}</div> <div className="product_tit">{getMessage('estimate.detail.header.specialEstimateProductInfo')}</div>
</div> </div>
<div className="left-unit-box">
<button className={`estimate-arr-btn down mr5 ${hidden ? '' : 'on'}`} onClick={() => setHidden(false)}></button>
<button className={`estimate-arr-btn up ${hidden ? 'on' : ''}`} onClick={() => setHidden(true)}></button>
</div> </div>
{/* 공통코드영역시작 */} </div>
{/* 견적 특이사항 코드영역시작 */}
<div className={`estimate-check-wrap ${hidden ? 'hide' : ''}`}>
<div className="estimate-check-inner">
<div className="special-note-check-wrap"></div> <div className="special-note-check-wrap"></div>
{/* 공통코드영역끝 */} {/* 견적특이사항 선택한 내용?영역시작 */}
{/* 견적특이사항 내용영역시작 */} <div className="calculation-estimate">
<div className="calculation-estimate"></div> <dl>
{/* 견적특이사항 내용영역끝 */} <dt>제목11??</dt>
{/* 견적특이사항 끝 */} <dd>제목1 비고</dd>
</dl>
<dl>
<dt>제목22??</dt>
<dd>제목2 비고</dd>
</dl>
</div>
{/* 견적특이사항 선택한 내용?영역끝 */}
</div>
</div>
{/* 견적 특이사항 코드영역 끝 */}
{/* 견적특이사항 영역끝 */}
{/* 제품정보 시작 */} {/* 제품정보 시작 */}
<div className="table-box-title-wrap"> <div className="table-box-title-wrap">
<div className="title-wrap"> <div className="title-wrap">
<h3>{getMessage('estimate.detail.header.specialEstimateProductInfo')}</h3> <h3>{getMessage('estimate.detail.header.specialEstimateProductInfo')}</h3>
</div> </div>
</div> </div>
<div className="estimate-wrap"> <div className="esimate-wrap">
<div className="estimate-list-wrap one"> <div className="estimate-list-wrap one">
<div className="estimate-box"> <div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totPcs')}</div> <div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totPcs')}</div>
@ -285,7 +409,7 @@ export default function Estimate({ params }) {
</div> </div>
</div> </div>
{/* YJOD면 아래영역 숨김 */} {/* YJOD면 아래영역 숨김 */}
<div className="common-table bt-able"> <div className="common-table bt-able" style={{ display: state?.estimateType === 'YJSS' ? '' : 'none' }}>
<table> <table>
<colgroup> <colgroup>
<col style={{ width: '160px' }} /> <col style={{ width: '160px' }} />
@ -319,18 +443,35 @@ export default function Estimate({ params }) {
<div className="estimate-product-option"> <div className="estimate-product-option">
<div className="product-price-wrap"> <div className="product-price-wrap">
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div> <div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
<div className="select-wrap"></div> <div className="select-wrap">
<select className="select-light" name="" id="">
<option value="">111</option>
<option value="">222</option>
</select>
</div>
<button className="btn-origin grey ml5">{getMessage('estimate.detail.showPrice.btn1')}</button> <button className="btn-origin grey ml5">{getMessage('estimate.detail.showPrice.btn1')}</button>
</div> </div>
<div className="product-edit-wrap"> <div className="product-edit-wrap">
<div className="product-edit-explane"> <ul className="product-edit-explane">
<div className="click-check"> <li className="explane-item item01">
<span className="ico"></span> <span className="ico"></span>
{getMessage('estimate.detail.showPrice.description')} {getMessage('estimate.detail.showPrice.description1')}
</div> </li>
</div> <li className="explane-item item02">
<span className="ico"></span>
{getMessage('estimate.detail.showPrice.description2')}
</li>
<li className="explane-item item03">
<span className="ico"></span>
{getMessage('estimate.detail.showPrice.description3')}
</li>
<li className="explane-item item04">
<span className="ico"></span>
{getMessage('estimate.detail.showPrice.description4')}
</li>
</ul>
<div className="product-edit-btn"> <div className="product-edit-btn">
<button className="btn-origin navy mr5"> <button className="btn-origin navy mr5" type="submit">
<span className="plus"></span> <span className="plus"></span>
{getMessage('estimate.detail.showPrice.btn2')} {getMessage('estimate.detail.showPrice.btn2')}
</button> </button>
@ -348,6 +489,7 @@ export default function Estimate({ params }) {
</div> </div>
</div> </div>
{/* 기본정보끝 */} {/* 기본정보끝 */}
{/* </form> */}
</div> </div>
</div> </div>
) )

View File

@ -32,6 +32,8 @@ import { menusState, menuTypeState } from '@/store/menuAtom'
import useMenu from '@/hooks/common/useMenu' import useMenu from '@/hooks/common/useMenu'
import { MENU } from '@/common/common' import { MENU } from '@/common/common'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
export default function CanvasMenu(props) { export default function CanvasMenu(props) {
const { menuNumber, setMenuNumber } = props const { menuNumber, setMenuNumber } = props
const pathname = usePathname() const pathname = usePathname()
@ -49,8 +51,9 @@ export default function CanvasMenu(props) {
const sessionState = useRecoilValue(sessionStore) const sessionState = useRecoilValue(sessionStore)
const globalLocale = useRecoilValue(globalLocaleStore) const globalLocale = useRecoilValue(globalLocaleStore)
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const { handleZoomClear } = useCanvasEvent() const { handleZoomClear, handleZoom } = useCanvasEvent()
const { handleMenu } = useMenu() const { handleMenu } = useMenu()
const { handleEstimateSubmit } = useEstimateController()
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { currentCanvasPlan, saveCanvas } = usePlan() const { currentCanvasPlan, saveCanvas } = usePlan()
@ -129,7 +132,7 @@ export default function CanvasMenu(props) {
const handlePopup = () => { const handlePopup = () => {
const id = uuidv4() const id = uuidv4()
addPopup(id, 0, <SettingModal01 id={id} />) addPopup(id, 1, <SettingModal01 id={id} />, true)
} }
useEffect(() => { useEffect(() => {
@ -201,14 +204,18 @@ export default function CanvasMenu(props) {
<button <button
className="control-btn minus" className="control-btn minus"
onClick={() => { onClick={() => {
canvas.setZoom(canvas.getZoom() - 0.1) handleZoom(false)
}} }}
></button> ></button>
<span>{canvasZoom}%</span> <span>{canvasZoom}%</span>
<button className="control-btn plus" onClick={handleZoomClear}></button> <button
className="control-btn plus"
onClick={() => {
handleZoom(true)
}}
></button>
</div> </div>
<div className="btn-from"> <div className="btn-from">
<button className="btn07" onClick={handleClear}></button>
<button className="btn08" onClick={handleSaveCanvas}></button> <button className="btn08" onClick={handleSaveCanvas}></button>
<button className="btn09"></button> <button className="btn09"></button>
</div> </div>
@ -222,7 +229,7 @@ export default function CanvasMenu(props) {
<span className="ico ico01"></span> <span className="ico ico01"></span>
<span>{getMessage('plan.menu.estimate.docDown')}</span> <span>{getMessage('plan.menu.estimate.docDown')}</span>
</button> </button>
<button className="btn-frame gray ico-flx"> <button className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
<span className="ico ico02"></span> <span className="ico ico02"></span>
<span>{getMessage('plan.menu.estimate.save')}</span> <span>{getMessage('plan.menu.estimate.save')}</span>
</button> </button>

View File

@ -50,7 +50,11 @@ export default function CircuitTrestleSetting({ id }) {
Next Next
</button> </button>
)} )}
{tabNum === 3 && <button className="btn-frame modal act">保存 (仮割り当て)</button>} {tabNum === 3 && (
<button className="btn-frame modal act">
{`${getMessage('modal.common.save')} (${getMessage('modal.circuit.trestle.setting.alloc.trestle')})`}
</button>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -114,7 +114,7 @@ export default function PowerConditionalSelect({ setTabNum }) {
</div> </div>
</div> </div>
<div className="circuit-right-wrap mb10"> <div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button> <button className="btn-frame self mr5">{getMessage('modal.common.add')}</button>
</div> </div>
<div className="circuit-data-form"> <div className="circuit-data-form">
<span className="normal-font"> <span className="normal-font">
@ -132,12 +132,12 @@ export default function PowerConditionalSelect({ setTabNum }) {
<div className="slope-wrap"> <div className="slope-wrap">
<div className="d-check-box pop mb15"> <div className="d-check-box pop mb15">
<input type="checkbox" id="ch03" /> <input type="checkbox" id="ch03" />
<label htmlFor="ch03">同一傾斜同一方面の面積の場合同じ面として回路を分ける</label> <label htmlFor="ch03"> {getMessage('modal.circuit.trestle.setting.power.conditional.select.check1')}</label>
</div> </div>
<div className="d-check-box pop"> <div className="d-check-box pop">
<input type="checkbox" id="ch04" /> <input type="checkbox" id="ch04" />
<label className="red" htmlFor="ch04"> <label className="red" htmlFor="ch04">
MAX接続過積で回路を分ける {getMessage('modal.circuit.trestle.setting.power.conditional.select.check2')}
</label> </label>
</div> </div>
</div> </div>

View File

@ -18,8 +18,8 @@ export default function StepUp({}) {
<table> <table>
<thead> <thead>
<tr> <tr>
<th>シリアル枚数</th> <th>{getMessage('modal.circuit.trestle.setting.step.up.allocation.serial.amount')}</th>
<th>総回路数</th> <th>{getMessage('modal.circuit.trestle.setting.step.up.allocation.total.amount')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -49,14 +49,14 @@ export default function StepUp({}) {
</div> </div>
<div className="circuit-table-flx-wrap"> <div className="circuit-table-flx-wrap">
<div className="circuit-table-flx-box"> <div className="circuit-table-flx-box">
<div className="bold-font mb10">接続する</div> <div className="bold-font mb10">{getMessage('modal.circuit.trestle.setting.step.up.allocation.connected')}</div>
<div className="roof-module-table mb10"> <div className="roof-module-table mb10">
<table> <table>
<thead> <thead>
<tr> <tr>
<th style={{ width: '140px' }}>名称</th> <th style={{ width: '140px' }}>{getMessage('modal.circuit.trestle.setting.power.conditional.select.name')}</th>
<th>回路数</th> <th>{getMessage('modal.circuit.trestle.setting.power.conditional.select.circuit.amount')}</th>
<th>昇圧回路数</th> <th>{getMessage('modal.circuit.trestle.setting.step.up.allocation.circuit.amount')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -80,7 +80,7 @@ export default function StepUp({}) {
</div> </div>
<div className="bottom-wrap"> <div className="bottom-wrap">
<div className="circuit-right-wrap mb10"> <div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button> <button className="btn-frame self mr5">{getMessage('modal.common.add')}</button>
</div> </div>
<div className="circuit-data-form"> <div className="circuit-data-form">
<span className="normal-font"> <span className="normal-font">
@ -90,13 +90,13 @@ export default function StepUp({}) {
</div> </div>
</div> </div>
<div className="circuit-table-flx-box"> <div className="circuit-table-flx-box">
<div className="bold-font mb10">昇圧オプション</div> <div className="bold-font mb10">{getMessage('modal.circuit.trestle.setting.step.up.allocation.option')}</div>
<div className="roof-module-table mb10"> <div className="roof-module-table mb10">
<table> <table>
<thead> <thead>
<tr> <tr>
<th>名称</th> <th>{getMessage('modal.circuit.trestle.setting.power.conditional.select.name')}</th>
<th>昇圧回路数</th> <th>{getMessage('modal.circuit.trestle.setting.step.up.allocation.circuit.amount')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -113,7 +113,7 @@ export default function StepUp({}) {
</div> </div>
<div className="bottom-wrap"> <div className="bottom-wrap">
<div className="circuit-right-wrap mb10"> <div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button> <button className="btn-frame self mr5">{getMessage('modal.common.add')}</button>
</div> </div>
<div className="circuit-data-form"> <div className="circuit-data-form">
<span className="normal-font"> <span className="normal-font">
@ -124,7 +124,7 @@ export default function StepUp({}) {
</div> </div>
</div> </div>
<div className="circuit-count-input"> <div className="circuit-count-input">
<span className="normal-font">綿調道区分</span> <span className="normal-font">{getMessage('modal.module.basic.setting.module.cotton.classification')}</span>
<div className="input-grid mr5" style={{ width: '40px' }}> <div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" /> <input type="text" className="input-origin block" />
</div> </div>
@ -395,7 +395,7 @@ export default function StepUp({}) {
<div className="slope-wrap"> <div className="slope-wrap">
<div className="outline-form"> <div className="outline-form">
<span className="mr10" style={{ width: 'auto' }}> <span className="mr10" style={{ width: 'auto' }}>
モニターの選択 {getMessage('modal.circuit.trestle.setting.step.up.allocation.select.monitor')}
</span> </span>
<div className="grid-select mr10"> <div className="grid-select mr10">
<QSelectBox title={'電力検出ユニット (モニター付き)'} option={SelectOption01} /> <QSelectBox title={'電力検出ユニット (モニター付き)'} option={SelectOption01} />

View File

@ -4,13 +4,13 @@ export default function PassivityCircuitAllocation() {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const moduleData = { const moduleData = {
header: [ header: [
{ name: getMessage('屋根面'), prop: 'roofShape' }, { name: getMessage('modal.panel.batch.statistic.roof.shape'), prop: 'roofShape' },
{ {
name: getMessage('Q.TRON M-G2'), name: getMessage('Q.TRON M-G2'),
prop: 'moduleName', prop: 'moduleName',
}, },
{ {
name: getMessage('発電量 (kW)'), name: `${getMessage('modal.panel.batch.statistic.power.generation.amount')}(kW)`,
prop: 'powerGeneration', prop: 'powerGeneration',
}, },
], ],

View File

@ -5,9 +5,7 @@ import { useMessage } from '@/hooks/useMessage'
import { canvasState, dotLineGridSettingState, dotLineIntervalSelector } from '@/store/canvasAtom' import { canvasState, dotLineGridSettingState, dotLineIntervalSelector } from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { onlyNumberInputChange } from '@/util/input-utils' import { onlyNumberInputChange } from '@/util/input-utils'
import { fabric } from 'fabric' import { settingModalGridOptionsState } from '@/store/settingAtom'
import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector, settingModalGridOptionsState } from '@/store/settingAtom'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
@ -33,7 +31,7 @@ export default function DotLineGrid(props) {
// const [modalOption, setModalOption] = useRecoilState(modalState); //modal state // const [modalOption, setModalOption] = useRecoilState(modalState); //modal state
const [objectNo, setObjectNo] = useState('test123240912001') // const [objectNo, setObjectNo] = useState('test123240912001') //
const [close, setClose] = useState(false) const [close, setClose] = useState(false)
const { id, setIsShow, pos = { x: 840, y: -815 } } = props const { id, setIsShow, pos = { x: 840, y: -815 }, isConfig = false } = props
const setSettingModalGridOptions = useSetRecoilState(settingModalGridOptionsState) const setSettingModalGridOptions = useSetRecoilState(settingModalGridOptionsState)
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -142,7 +140,7 @@ export default function DotLineGrid(props) {
await post({ url: `/api/canvas-management/canvas-grid-settings`, data: patternData }).then((res) => { await post({ url: `/api/canvas-management/canvas-grid-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) }) swalFire({ text: getMessage(res.returnMessage) })
setDotLineGridSettingState({ ...currentSetting }) setDotLineGridSettingState({ ...currentSetting })
closePopup(id) closePopup(id, isConfig)
}) })
} catch (error) { } catch (error) {
swalFire({ text: getMessage(res.returnMessage), icon: 'error' }) swalFire({ text: getMessage(res.returnMessage), icon: 'error' })
@ -213,7 +211,7 @@ export default function DotLineGrid(props) {
className="modal-close" className="modal-close"
onClick={() => { onClick={() => {
setIsShow(false) setIsShow(false)
closePopup(id) closePopup(id, isConfig)
}} }}
> >
닫기 닫기

View File

@ -78,14 +78,14 @@ export default function GridOption() {
// //
setShowDotLineGridModal(selectedOption.selected) setShowDotLineGridModal(selectedOption.selected)
addPopup(dotLineId, 2, <DotLineGrid {...dotLineGridProps} />) addPopup(dotLineId, 2, <DotLineGrid {...dotLineGridProps} />, true)
} else if (selectedOption.id === 3) { } else if (selectedOption.id === 3) {
// //
setAdsorptionPointAddMode(selectedOption.selected) setAdsorptionPointAddMode(selectedOption.selected)
} else if (selectedOption.id === 4) { } else if (selectedOption.id === 4) {
// //
setShowColorPickerModal(selectedOption.selected) setShowColorPickerModal(selectedOption.selected)
addPopup(colorId, 2, <ColorPickerModal {...colorPickerProps} />) addPopup(colorId, 2, <ColorPickerModal {...colorPickerProps} />, true)
} }
setGridOptions(newGridOptions) setGridOptions(newGridOptions)
@ -94,6 +94,7 @@ export default function GridOption() {
const dotLineGridProps = { const dotLineGridProps = {
id: dotLineId, id: dotLineId,
setIsShow: setShowDotLineGridModal, setIsShow: setShowDotLineGridModal,
isConfig: true,
pos: { pos: {
x: 845, x: 845,
y: 180, y: 180,
@ -106,6 +107,7 @@ export default function GridOption() {
setColor: setGridColor, setColor: setGridColor,
isShow: showColorPickerModal, isShow: showColorPickerModal,
setIsShow: setShowColorPickerModal, setIsShow: setShowColorPickerModal,
isConfig: true,
pos: { pos: {
x: 785, x: 785,
y: 180, y: 180,

View File

@ -1,4 +1,4 @@
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { useRecoilValue } from 'recoil'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import DimensionLineSetting from '@/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting' import DimensionLineSetting from '@/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting'
@ -67,6 +67,7 @@ export default function SecondOption() {
id: fontId, id: fontId,
pos: { x: 745, y: 180 }, pos: { x: 745, y: 180 },
setIsShow: setShowFontSettingModal, setIsShow: setShowFontSettingModal,
isConfig: true,
} }
const planSizeProps = { const planSizeProps = {
id: planSizeId, id: planSizeId,
@ -86,36 +87,41 @@ export default function SecondOption() {
case 'font1': { case 'font1': {
// //
setShowFontSettingModal(true) setShowFontSettingModal(true)
setShowDimensionLineSettingModal(false)
fontProps.type = 'commonText' fontProps.type = 'commonText'
fontProps.id = fontId + 1 fontProps.id = fontId + 1
addPopup(fontId + 1, 2, <FontSetting {...fontProps} />) addPopup(fontId + 1, 2, <FontSetting {...fontProps} />, true)
break break
} }
case 'font2': { case 'font2': {
// //
setShowFontSettingModal(true) setShowFontSettingModal(true)
setShowDimensionLineSettingModal(false)
fontProps.type = 'flowText' fontProps.type = 'flowText'
fontProps.id = fontId + 2 fontProps.id = fontId + 2
addPopup(fontId + 2, 2, <FontSetting {...fontProps} />) addPopup(fontId + 2, 2, <FontSetting {...fontProps} />, true)
break break
} }
case 'font3': { case 'font3': {
// //
setShowFontSettingModal(true) setShowFontSettingModal(true)
setShowDimensionLineSettingModal(false)
fontProps.type = 'lengthText' fontProps.type = 'lengthText'
fontProps.id = fontId + 3 fontProps.id = fontId + 3
addPopup(fontId + 3, 2, <FontSetting {...fontProps} />) addPopup(fontId + 3, 2, <FontSetting {...fontProps} />, true)
break break
} }
case 'font4': { case 'font4': {
// //
setShowFontSettingModal(true) setShowFontSettingModal(true)
setShowDimensionLineSettingModal(false)
fontProps.type = 'circuitNumberText' fontProps.type = 'circuitNumberText'
fontProps.id = fontId fontProps.id = fontId
addPopup(fontId, 2, <FontSetting {...fontProps} />) addPopup(fontId, 2, <FontSetting {...fontProps} />, true)
break break
} }
@ -123,7 +129,7 @@ export default function SecondOption() {
// //
if (!showDimensionLineSettingModal) { if (!showDimensionLineSettingModal) {
setShowDimensionLineSettingModal(true) setShowDimensionLineSettingModal(true)
addPopup(dimensionId, 2, <DimensionLineSetting {...dimensionProps} />) addPopup(dimensionId, 2, <DimensionLineSetting {...dimensionProps} />, true)
} else { } else {
setShowDimensionLineSettingModal(false) setShowDimensionLineSettingModal(false)
closePopup(dimensionId) closePopup(dimensionId)
@ -134,7 +140,8 @@ export default function SecondOption() {
case 'planSize': { case 'planSize': {
// //
setShowPlanSizeSettingModal(true) setShowPlanSizeSettingModal(true)
addPopup(planSizeId, 2, <PlanSizeSetting {...planSizeProps} />) setShowDimensionLineSettingModal(false)
addPopup(planSizeId, 2, <PlanSizeSetting {...planSizeProps} />, true)
break break
} }
} }

View File

@ -11,7 +11,7 @@ import { useRecoilValue } from 'recoil'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
export default function SettingModal01(props) { export default function SettingModal01(props) {
const { setShowDotLineGridModal, setShowFontSettingModal, id } = props const { setShowDotLineGridModal, setShowFontSettingModal, id, isConfig } = props
console.log(props) console.log(props)
const [buttonAct, setButtonAct] = useState(1) const [buttonAct, setButtonAct] = useState(1)
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -27,7 +27,7 @@ export default function SettingModal01(props) {
<div className={`modal-pop-wrap sm mount`}> <div className={`modal-pop-wrap sm mount`}>
<div className="modal-head"> <div className="modal-head">
<h1 className="title">{getMessage('modal.canvas.setting')}</h1> <h1 className="title">{getMessage('modal.canvas.setting')}</h1>
<button className="modal-close" onClick={() => closePopup(id)}> <button className="modal-close" onClick={() => closePopup(id, true)}>
닫기 닫기
</button> </button>
</div> </div>

View File

@ -87,6 +87,7 @@ export default function DimensionLineSetting(props) {
setFontColor: setOriginFontColor, setFontColor: setOriginFontColor,
fontSize: originFontSize, fontSize: originFontSize,
setFontSize: setOriginFontSize, setFontSize: setOriginFontSize,
isConfig: true,
id: fontModalId, id: fontModalId,
pos: { pos: {
x: 455, x: 455,
@ -97,17 +98,17 @@ export default function DimensionLineSetting(props) {
const popupHandle = (type) => { const popupHandle = (type) => {
switch (type) { switch (type) {
case 'color': case 'color':
addPopup(colorModalId, 3, <ColorPickerModal {...colorPickerProps} />) addPopup(colorModalId, 3, <ColorPickerModal {...colorPickerProps} />, true)
break break
case 'font': case 'font':
addPopup(fontModalId, 3, <FontSetting {...fontProps} />) addPopup(fontModalId, 3, <FontSetting {...fontProps} />, true)
break break
} }
} }
return ( return (
<WithDraggable isShow={true} pos={pos}> <WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap xxxm`}> <div className={`modal-pop-wrap xxxm mount`}>
<div className="modal-head"> <div className="modal-head">
<h1 className="title">{getMessage('modal.canvas.setting.font.plan.absorption.dimension.line')} </h1> <h1 className="title">{getMessage('modal.canvas.setting.font.plan.absorption.dimension.line')} </h1>
<button <button

View File

@ -15,14 +15,14 @@ export default function PlanSizeSetting(props) {
return ( return (
<WithDraggable isShow={true} pos={pos}> <WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap xsm`}> <div className={`modal-pop-wrap xsm mount`}>
<div className="modal-head"> <div className="modal-head">
<h1 className="title">{getMessage('modal.canvas.setting.font.plan.absorption.plan.size.setting')}</h1> <h1 className="title">{getMessage('modal.canvas.setting.font.plan.absorption.plan.size.setting')}</h1>
<button <button
className="modal-close" className="modal-close"
onClick={() => { onClick={() => {
setIsShow(false) setIsShow(false)
closePopup(id) closePopup(id, true)
}} }}
> >
닫기 닫기

View File

@ -0,0 +1,153 @@
import { useAxios } from '@/hooks/useAxios'
import { useEffect, useReducer, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { globalLocaleStore } from '@/store/localeAtom'
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { isObjectNotEmpty } from '@/util/common-utils'
const reducer = (prevState, nextState) => {
return { ...prevState, ...nextState }
}
// Constants
const ESTIMATE_API_ENDPOINT = '/api/estimates' // API 엔드포인트 정의
const defaultEstimateData = {
estimateDate: new Date(), //견적일
charger: '', //담당자
objectName: '', //안건명
objectNameOmit: '', //경칭코드
estimateType: 'YJOD', //주문분류
remarks: '', //비고
estimateOption: '', //견적특이사항
// itemList: [{ id: 1, name: '' }],
//아이템에 필요없는거 빼기
itemList: [
{
amount: '',
fileUploadFlg: '',
itemChangeFlg: '',
itemGroup: '',
itemId: '', //키값??
itemName: '',
itemNo: '',
moduleFlg: '',
objectNo: '',
pkgMaterialFlg: '',
planNo: '',
pnowW: '',
salePrice: '',
saleTotPrice: '',
specification: '',
unit: '',
},
],
}
// Helper functions
// const updateItemInList = (itemList, id, updates) => {
const updateItemInList = (itemList, itemId, updates) => {
// return itemList.map((item) => (item.id === id ? { ...item, ...updates } : item))
return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item))
}
export const useEstimateController = (planNo) => {
const globalLocaleState = useRecoilValue(globalLocaleStore)
const objectRecoil = useRecoilValue(floorPlanObjectState)
const [estimateData, setEstimateData] = useRecoilState(estimateState)
const { get, post } = useAxios(globalLocaleState)
const [isLoading, setIsLoading] = useState(false)
const { promisePost } = useAxios()
const [state, setState] = useReducer(reducer, defaultEstimateData)
useEffect(() => {
if (!isLoading) {
if (objectRecoil.floorPlanObjectNo && planNo) {
fetchSetting()
}
}
}, [])
// 상세 조회
const fetchSetting = async () => {
try {
await get({ url: `/api/estimate/${objectRecoil.floorPlanObjectNo}/${planNo}/detail` }).then((res) => {
if (isObjectNotEmpty(res)) {
setState(res)
}
})
setIsLoading(true)
} catch (error) {
console.error('견적서 상세조회 Error: ', error)
setIsLoading(true)
}
}
// const updateItem = (id, updates) => {
const updateItem = (itemId, updates) => {
setState({
// itemList: updateItemInList(state.itemList, id, updates),
itemList: updateItemInList(state.itemList, itemId, updates),
})
}
const addItem = () => {
// const newId = Math.max(...state.itemList.map((item) => item.id)) + 1
const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1
setState({
// itemList: [...state.itemList, { id: newId, name: '' }],
//셋팅할필요없는거 빼기
itemList: [
...state.itemList,
{
itemId: newItemId,
amount: '',
fileUploadFlg: '',
itemChangeFlg: '',
itemGroup: '',
itemName: '',
itemNo: '',
moduleFlg: '',
objectNo: '',
pkgMaterialFlg: '',
planNo: '',
pnowW: '',
salePrice: '',
saleTotPrice: '',
specification: '',
unit: '',
},
],
})
}
useEffect(() => {
setEstimateData({ ...state })
}, [state])
//견적서 저장
const handleEstimateSubmit = async () => {
console.log('::담긴 estimateData:::', estimateData)
return
try {
const result = await promisePost({
url: ESTIMATE_API_ENDPOINT,
data: estimateData,
})
return result
} catch (error) {
console.error('Failed to submit estimate:', error)
throw error
}
}
return {
state,
setState,
updateItem,
addItem,
handleEstimateSubmit,
fetchSetting,
}
}

View File

@ -223,9 +223,9 @@ export function useCanvasSetting() {
const option1 = settingModalFirstOptions.option1 const option1 = settingModalFirstOptions.option1
// 'allocDisplay' 할당 표시 // 'allocDisplay' 할당 표시
// 'outlineDisplay' 외벽선 표시 'outerLine', 'wallLine' // 'outlineDisplay' 외벽선 표시 'outerLine', POLYGON_TYPE.WALL
// 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid' // 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid'
// 'lineDisplay' 지붕선 표시 'roof', 'roofBase' // 'lineDisplay' 지붕선 표시 'roof', POLYGON_TYPE.ROOF
// 'wordDisplay' 문자 표시 // 'wordDisplay' 문자 표시
// 'circuitNumDisplay' 회로번호 표시 // 'circuitNumDisplay' 회로번호 표시
// 'flowDisplay' 흐름방향 표시 'arrow' // 'flowDisplay' 흐름방향 표시 'arrow'
@ -248,7 +248,7 @@ export function useCanvasSetting() {
optionName = ['lindGrid', 'dotGrid'] optionName = ['lindGrid', 'dotGrid']
break break
case 'lineDisplay': //지붕선 표시 case 'lineDisplay': //지붕선 표시
optionName = ['roof', 'roofBase'] optionName = ['roof', POLYGON_TYPE.ROOF]
break break
case 'wordDisplay': //문자 표시 case 'wordDisplay': //문자 표시
optionName = ['6'] optionName = ['6']

View File

@ -13,9 +13,9 @@ export function useFirstOption() {
const option1 = settingModalFirstOptions.option1 const option1 = settingModalFirstOptions.option1
// 'allocDisplay' 할당 표시 // 'allocDisplay' 할당 표시
// 'outlineDisplay' 외벽선 표시 'outerLine', 'wallLine' // 'outlineDisplay' 외벽선 표시 'outerLine', POLYGON_TYPE.WALL
// 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid' // 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid'
// 'lineDisplay' 지붕선 표시 'roof', 'roofBase' // 'lineDisplay' 지붕선 표시 'roof', POLYGON_TYPE.ROOF
// 'wordDisplay' 문자 표시 // 'wordDisplay' 문자 표시
// 'circuitNumDisplay' 회로번호 표시 // 'circuitNumDisplay' 회로번호 표시
// 'flowDisplay' 흐름방향 표시 'arrow' // 'flowDisplay' 흐름방향 표시 'arrow'
@ -37,7 +37,7 @@ export function useFirstOption() {
optionName = ['lineGrid', 'dotGrid', 'adsorptionPoint', 'tempGrid'] optionName = ['lineGrid', 'dotGrid', 'adsorptionPoint', 'tempGrid']
break break
case 'lineDisplay': //지붕선 표시 case 'lineDisplay': //지붕선 표시
optionName = ['roof', 'roofBase'] optionName = ['roof', POLYGON_TYPE.ROOF]
break break
case 'wordDisplay': //문자 표시 case 'wordDisplay': //문자 표시
optionName = ['6'] optionName = ['6']

View File

@ -15,7 +15,7 @@ import {
outerLineLength2State, outerLineLength2State,
outerLineTypeState, outerLineTypeState,
} from '@/store/outerLineAtom' } from '@/store/outerLineAtom'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, polygonToTurfPolygon } from '@/util/canvas-util' import { calculateIntersection, distanceBetweenPoints, findClosestPoint, isPointOnLine, polygonToTurfPolygon } from '@/util/canvas-util'
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
@ -23,6 +23,7 @@ import { booleanPointInPolygon } from '@turf/turf'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { calculateAngle } from '@/util/qpolygon-utils' import { calculateAngle } from '@/util/qpolygon-utils'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import { POLYGON_TYPE } from '@/common/common'
// 보조선 작성 // 보조선 작성
export function useAuxiliaryDrawing(id) { export function useAuxiliaryDrawing(id) {
@ -80,7 +81,7 @@ export function useAuxiliaryDrawing(id) {
useEffect(() => { useEffect(() => {
// innerLines가 있을경우 삭제 // innerLines가 있을경우 삭제
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roofBase') const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
if (roofs.length === 0) { if (roofs.length === 0) {
swalFire({ text: '지붕형상이 없습니다.' }) swalFire({ text: '지붕형상이 없습니다.' })
closePopup(id) closePopup(id)
@ -561,7 +562,7 @@ export function useAuxiliaryDrawing(id) {
return return
} }
const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
/*const allLines = [...auxiliaryLines] /*const allLines = [...auxiliaryLines]
roofBases.forEach((roofBase) => { roofBases.forEach((roofBase) => {
@ -611,9 +612,41 @@ export function useAuxiliaryDrawing(id) {
}, },
) )
lineHistory.current.push(newLine) lineHistory.current.push(newLine)
lineHistory.current = lineHistory.current.filter((history) => history !== line1)
removeLine(line1) removeLine(line1)
intersectionPoints.current.push(...interSectionPointsWithRoofLines) intersectionPoints.current.push(...interSectionPointsWithRoofLines)
return return
} else if (interSectionPointsWithRoofLines.length === 1) {
//지붕선과 만나는 점이 하나일 경우
const distance1 = distanceBetweenPoints({ x: line1.x1, y: line1.y1 }, interSectionPointsWithRoofLines[0])
const distance2 = distanceBetweenPoints({ x: line1.x2, y: line1.y2 }, interSectionPointsWithRoofLines[0])
if (!(distance1 === 0 || distance2 === 0)) {
if (distance1 >= distance2) {
const newLine = addLine([line1.x1, line1.y1, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], {
stroke: 'black',
strokeWidth: 1,
selectable: false,
name: 'auxiliaryLine',
isFixed: true,
})
lineHistory.current.push(newLine)
lineHistory.current = lineHistory.current.filter((history) => history !== line1)
removeLine(line1)
} else {
const newLine = addLine([line1.x2, line1.y2, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], {
stroke: 'black',
strokeWidth: 1,
selectable: false,
name: 'auxiliaryLine',
isFixed: true,
})
lineHistory.current.push(newLine)
lineHistory.current = lineHistory.current.filter((history) => history !== line1)
removeLine(line1)
}
intersectionPoints.current.push(interSectionPointsWithRoofLines[0])
}
} }
//보조선과 만나는 점을 찾는다. //보조선과 만나는 점을 찾는다.
@ -659,6 +692,7 @@ export function useAuxiliaryDrawing(id) {
}) })
} }
lineHistory.current.push(newLine) lineHistory.current.push(newLine)
lineHistory.current = lineHistory.current.filter((history) => history !== line1)
removeLine(line1) removeLine(line1)
}) })
@ -742,7 +776,7 @@ export function useAuxiliaryDrawing(id) {
return return
} }
const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
//lineHistory.current에 있는 선들 중 startPoint와 endPoint가 겹치는 line은 제거 //lineHistory.current에 있는 선들 중 startPoint와 endPoint가 겹치는 line은 제거
// 겹치는 선 하나는 canvas에서 제거한다. // 겹치는 선 하나는 canvas에서 제거한다.
@ -772,9 +806,13 @@ export function useAuxiliaryDrawing(id) {
}) })
const roofInnerLines = innerLines.filter((line) => { const roofInnerLines = innerLines.filter((line) => {
const inPolygon1 = const inPolygon1 =
tempPolygonPoints.some((point) => point.x === line.x1 && point.y === line.y1) || roofBase.inPolygon({ x: line.x1, y: line.y1 }) tempPolygonPoints.some((point) => point.x === line.x1 && point.y === line.y1) ||
roofBase.inPolygon({ x: line.x1, y: line.y1 }) ||
roofBase.lines.some((line) => isPointOnLine(line, { x: line.x1, y: line.y1 }))
const inPolygon2 = const inPolygon2 =
tempPolygonPoints.some((point) => point.x === line.x2 && point.y === line.y2) || roofBase.inPolygon({ x: line.x2, y: line.y2 }) tempPolygonPoints.some((point) => point.x === line.x2 && point.y === line.y2) ||
roofBase.inPolygon({ x: line.x2, y: line.y2 }) ||
roofBase.lines.some((line) => isPointOnLine(line, { x: line.x2, y: line.y2 }))
if (inPolygon1 && inPolygon2) { if (inPolygon1 && inPolygon2) {
line.attributes = { ...line.attributes, roofId: roofBase.id } line.attributes = { ...line.attributes, roofId: roofBase.id }

View File

@ -160,7 +160,7 @@ export function useEavesGableEdit(id) {
attributes, attributes,
}) })
const roofBases = canvas?.getObjects().filter((obj) => obj.name === 'roofBase') const roofBases = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
roofBases.forEach((roof) => { roofBases.forEach((roof) => {
roof.innerLines.forEach((line) => { roof.innerLines.forEach((line) => {

View File

@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { useRecoilValue, useResetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { canvasState, currentObjectState } from '@/store/canvasAtom' import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useMode } from '@/hooks/useMode' import { useMode } from '@/hooks/useMode'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'

View File

@ -82,12 +82,12 @@ export function useRoofAllocationSetting(id) {
const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0]) const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0])
useEffect(() => { useEffect(() => {
const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
if (roofBases.length === 0) { if (roofBases.length === 0) {
swalFire({ text: '할당할 지붕이 없습니다.' }) swalFire({ text: '할당할 지붕이 없습니다.' })
closePopup(id) closePopup(id)
} }
// if (type === 'roofBase') { // if (type === POLYGON_TYPE.ROOF) {
// // 지붕면 할당 // // 지붕면 할당
// //
// } else if ('roof') { // } else if ('roof') {
@ -105,7 +105,7 @@ export function useRoofAllocationSetting(id) {
// 선택한 지붕재로 할당 // 선택한 지붕재로 할당
const handleSave = () => { const handleSave = () => {
const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
roofBases.forEach((roofBase) => { roofBases.forEach((roofBase) => {
try { try {
@ -118,7 +118,7 @@ export function useRoofAllocationSetting(id) {
canvas.remove(line) canvas.remove(line)
}) })
canvas.remove(roofBase) // canvas.remove(roofBase)
}) })
wallLines.forEach((wallLine) => { wallLines.forEach((wallLine) => {

View File

@ -185,7 +185,7 @@ export function useRoofShapePassivitySetting(id) {
} }
const handleLineToPolygon = () => { const handleLineToPolygon = () => {
const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine') const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine')
const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
exceptObjs.forEach((obj) => { exceptObjs.forEach((obj) => {

View File

@ -383,7 +383,7 @@ export function useRoofShapeSetting(id) {
canvas canvas
.getObjects() .getObjects()
.filter((obj) => obj.name === 'roofBase') .filter((obj) => obj.name === POLYGON_TYPE.ROOF)
.forEach((obj) => { .forEach((obj) => {
canvas.remove(...obj.innerLines) canvas.remove(...obj.innerLines)
canvas.remove(obj) canvas.remove(obj)

View File

@ -1,4 +1,4 @@
import { useState } from 'react' import { useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil' import { useRecoilState, useRecoilValue } from 'recoil'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { canvasSizeState, canvasState, canvasZoomState, currentObjectState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' import { canvasSizeState, canvasState, canvasZoomState, currentObjectState, fontFamilyState, fontSizeState } from '@/store/canvasAtom'
@ -18,6 +18,10 @@ export function useCanvasEvent() {
const lengthTextOption = useRecoilValue(fontSelector('lengthText')) const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan() const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan()
useEffect(() => {
canvas?.setZoom(canvasZoom / 100)
}, [canvasZoom])
// 기본적인 이벤트 필요시 추가 // 기본적인 이벤트 필요시 추가
const attachDefaultEventOnCanvas = () => { const attachDefaultEventOnCanvas = () => {
removeEventOnCanvas() removeEventOnCanvas()
@ -365,6 +369,14 @@ export function useCanvasEvent() {
}) })
} }
const handleZoom = (isZoom) => {
if (isZoom) {
setCanvasZoom(canvasZoom + 10)
} else {
setCanvasZoom(canvasZoom - 10)
}
}
const handleZoomClear = () => { const handleZoomClear = () => {
setCanvasZoom(100) setCanvasZoom(100)
canvas.set({ zoom: 1 }) canvas.set({ zoom: 1 })
@ -376,5 +388,6 @@ export function useCanvasEvent() {
setCanvasForEvent, setCanvasForEvent,
attachDefaultEventOnCanvas, attachDefaultEventOnCanvas,
handleZoomClear, handleZoomClear,
handleZoom,
} }
} }

View File

@ -244,7 +244,7 @@ export function useContextMenu() {
if (temp.length > 0) menu = temp if (temp.length > 0) menu = temp
} }
handleClick(null, menu) if (menu) handleClick(null, menu)
} }
useEffect(() => { useEffect(() => {
@ -256,8 +256,6 @@ export function useContextMenu() {
}, [currentContextMenu]) }, [currentContextMenu])
useEffect(() => { useEffect(() => {
console.log('currentObject', currentObject)
if (currentObject?.name) { if (currentObject?.name) {
console.log(currentObject?.name) console.log(currentObject?.name)
switch (currentObject.name) { switch (currentObject.name) {
@ -477,7 +475,7 @@ export function useContextMenu() {
{ {
id: 'dimensionLineDisplayEdit', id: 'dimensionLineDisplayEdit',
name: getMessage('contextmenu.display.edit'), name: getMessage('contextmenu.display.edit'),
component: <DimensionLineSetting id={popupId} />, component: <DimensionLineSetting id={popupId} isConfig={false} />,
}, },
], ],
]) ])

View File

@ -4,47 +4,90 @@ import { contextPopupState, popupState } from '@/store/popupAtom'
export function usePopup() { export function usePopup() {
const [popup, setPopup] = useRecoilState(popupState) const [popup, setPopup] = useRecoilState(popupState)
const [contextMenuPopup, setContextMenuPopup] = useRecoilState(contextPopupState) const [contextMenuPopup, setContextMenuPopup] = useRecoilState(contextPopupState)
const addPopup = (id, depth, component) => {
setPopup({ children: [...filterDepth(depth), { id: id, depth: depth, component: component }] }) const addPopup = (id, depth, component, isConfig = false) => {
setPopup({
config: isConfig ? [...filterDepth(depth, isConfig), { id, depth, component, isConfig }] : [...popup.config],
other: !isConfig ? [...filterDepth(depth, isConfig), { id, depth, component, isConfig }] : [...popup.other],
})
} }
const closePopup = (id) => { const closePopup = (id, isConfig = false) => {
if (contextMenuPopup) setContextMenuPopup(null) if (contextMenuPopup) setContextMenuPopup(null)
setPopup({ children: [...filterChildrenPopup(id).filter((child) => child.id !== id)] }) if (isConfig) {
setPopup({
config: [...filterChildrenPopup(id, isConfig).filter((child) => child.id !== id)],
other: popup.other,
})
} else {
setPopup({
config: popup.config,
other: [...filterChildrenPopup(id, isConfig).filter((child) => child.id !== id)],
})
}
} }
const filterPopup = (depth) => { const filterPopup = (depth) => {
setPopup({ children: [...filterDepth(depth)] }) setPopup({
config: [...filterDepth(depth)],
other: [],
})
}
const filterChildrenPopup = (id, isConfig) => {
let target = []
if (isConfig) {
target = popup?.config.filter((child) => child.id === id)
} else {
target = popup?.other.filter((child) => child.id === id)
} }
const filterChildrenPopup = (id) => {
const target = popup.children.filter((child) => child.id === id)
if (target.length !== 0) { if (target.length !== 0) {
return popup.children.filter((child) => child.depth <= target[0].depth) if (isConfig) {
return popup?.config.filter((child) => child.depth <= target[0].depth)
} else {
return popup?.other.filter((child) => child.depth <= target[0].depth)
}
} else {
if (isConfig) {
return popup.config
} else {
return popup.other
}
} }
return popup.children
} }
const closePopups = (ids) => { const closePopups = (ids) => {
setPopup({ children: [...popup.children.filter((child) => !ids.includes(child.id))] }) setPopup({
config: [...popup?.config.filter((child) => !ids.includes(child.id))],
other: [...popup?.other.filter((child) => !ids.includes(child.id))],
})
} }
const closeAll = () => { const closeAll = () => {
setPopup({ children: [] }) setPopup({
other: [],
config: [],
})
} }
const closePrevPopup = () => { const closePrevPopup = () => {
setPopup({ children: [...popup.children.slice(popup.children.length - 1)] }) setPopup({
config: [...popup?.slice(popup?.length - 1)],
other: [],
})
} }
const filterDepth = (depth) => { const filterDepth = (depth, isConfig) => {
return [...popup.children.filter((child) => child.depth !== depth)] if (isConfig) {
return [...popup?.config.filter((child) => child.depth < depth)]
} else {
return [...popup?.other.filter((child) => child.depth < depth)]
}
} }
return { return {
popup, popup,
setPopup,
addPopup, addPopup,
closePopup, closePopup,
closePopups, closePopups,

View File

@ -123,6 +123,7 @@
"modal.module.basic.setting.auto.placement": "設定値に自動配置", "modal.module.basic.setting.auto.placement": "設定値に自動配置",
"plan.menu.module.circuit.setting.circuit.trestle.setting": "回路と架台の設定", "plan.menu.module.circuit.setting.circuit.trestle.setting": "回路と架台の設定",
"modal.circuit.trestle.setting": "回路と架台設定", "modal.circuit.trestle.setting": "回路と架台設定",
"modal.circuit.trestle.setting.alloc.trestle": "仮割り当て",
"modal.circuit.trestle.setting.power.conditional.select": "パワーコンディショナーを選択", "modal.circuit.trestle.setting.power.conditional.select": "パワーコンディショナーを選択",
"modal.circuit.trestle.setting.power.conditional.select.name": "名称", "modal.circuit.trestle.setting.power.conditional.select.name": "名称",
"modal.circuit.trestle.setting.power.conditional.select.rated.output": "定格出力", "modal.circuit.trestle.setting.power.conditional.select.rated.output": "定格出力",
@ -130,6 +131,8 @@
"modal.circuit.trestle.setting.power.conditional.select.max.connection": "最大接続枚数", "modal.circuit.trestle.setting.power.conditional.select.max.connection": "最大接続枚数",
"modal.circuit.trestle.setting.power.conditional.select.max.overload": "過積最大枚数", "modal.circuit.trestle.setting.power.conditional.select.max.overload": "過積最大枚数",
"modal.circuit.trestle.setting.power.conditional.select.output.current": "出力電流", "modal.circuit.trestle.setting.power.conditional.select.output.current": "出力電流",
"modal.circuit.trestle.setting.power.conditional.select.check1": "同一傾斜同一方面の面積の場合、同じ面として回路を分ける。",
"modal.circuit.trestle.setting.power.conditional.select.check2": "MAX接続過積で回路を分ける。",
"modal.circuit.trestle.setting.circuit.allocation": "回路割り当て", "modal.circuit.trestle.setting.circuit.allocation": "回路割り当て",
"modal.circuit.trestle.setting.circuit.allocation.auto": "自動回路割り当て", "modal.circuit.trestle.setting.circuit.allocation.auto": "自動回路割り当て",
"modal.circuit.trestle.setting.circuit.allocation.passivity": "手動回路割当", "modal.circuit.trestle.setting.circuit.allocation.passivity": "手動回路割当",
@ -140,6 +143,12 @@
"modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "すべての回路番号の初期化", "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "すべての回路番号の初期化",
"modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "番号確定", "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "番号確定",
"modal.circuit.trestle.setting.step.up.allocation": "昇圧設定", "modal.circuit.trestle.setting.step.up.allocation": "昇圧設定",
"modal.circuit.trestle.setting.step.up.allocation.serial.amount": "シリアル枚数",
"modal.circuit.trestle.setting.step.up.allocation.total.amount": "総回路数",
"modal.circuit.trestle.setting.step.up.allocation.connected": "接続する",
"modal.circuit.trestle.setting.step.up.allocation.circuit.amount": "昇圧回路数",
"modal.circuit.trestle.setting.step.up.allocation.option": "昇圧オプション",
"modal.circuit.trestle.setting.step.up.allocation.select.monitor": "モニターの選択",
"plan.menu.module.circuit.setting.plan.orientation": "図面方位の適用", "plan.menu.module.circuit.setting.plan.orientation": "図面方位の適用",
"plan.menu.estimate": "見積", "plan.menu.estimate": "見積",
"plan.menu.estimate.roof.alloc": "屋根面の割り当て", "plan.menu.estimate.roof.alloc": "屋根面の割り当て",
@ -474,6 +483,19 @@
"commons.east": "ドン", "commons.east": "ドン",
"commons.south": "南", "commons.south": "南",
"commons.north": "北", "commons.north": "北",
"font.style.normal": "보통(JA)",
"font.style.italic": "기울임꼴(JA)",
"font.style.bold": "굵게(JA)",
"font.style.bold.italic": "굵은 기울임꼴(JA)",
"color.black": "검정색(JA)",
"color.red": "빨강색(JA)",
"color.blue": "파랑색(JA)",
"color.gray": "회색(JA)",
"color.yellow": "황색(JA)",
"color.green": "녹색(JA)",
"color.pink": "분홍색(JA)",
"color.gold": "황금색(JA)",
"color.darkblue": "남색(JA)",
"site.name": "Q.CAST III", "site.name": "Q.CAST III",
"site.sub_name": "太陽光発電システム図面管理サイト", "site.sub_name": "太陽光発電システム図面管理サイト",
"board.notice.title": "お知らせ", "board.notice.title": "お知らせ",
@ -814,7 +836,10 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)", "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
"estimate.detail.header.showPrice": "価格表示", "estimate.detail.header.showPrice": "価格表示",
"estimate.detail.showPrice.btn1": "Pricing", "estimate.detail.showPrice.btn1": "Pricing",
"estimate.detail.showPrice.description": "クリックして製品の特異性を確認する", "estimate.detail.showPrice.description1": "製品価格 OPEN",
"estimate.detail.showPrice.description2": "追加, 変更資材",
"estimate.detail.showPrice.description3": "添付必須",
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
"estimate.detail.showPrice.btn2": "製品を追加", "estimate.detail.showPrice.btn2": "製品を追加",
"estimate.detail.showPrice.btn3": "製品削除" "estimate.detail.showPrice.btn3": "製品削除"
} }

View File

@ -127,6 +127,7 @@
"modal.module.basic.setting.auto.placement": "설정값으로 자동 배치", "modal.module.basic.setting.auto.placement": "설정값으로 자동 배치",
"plan.menu.module.circuit.setting.circuit.trestle.setting": "회로 및 가대 설정", "plan.menu.module.circuit.setting.circuit.trestle.setting": "회로 및 가대 설정",
"modal.circuit.trestle.setting": "회로 및 가대설정", "modal.circuit.trestle.setting": "회로 및 가대설정",
"modal.circuit.trestle.setting.alloc.trestle": "가대할당",
"modal.circuit.trestle.setting.power.conditional.select": "파워컨디셔너 선택", "modal.circuit.trestle.setting.power.conditional.select": "파워컨디셔너 선택",
"modal.circuit.trestle.setting.power.conditional.select.name": "명칭", "modal.circuit.trestle.setting.power.conditional.select.name": "명칭",
"modal.circuit.trestle.setting.power.conditional.select.rated.output": "정격출력", "modal.circuit.trestle.setting.power.conditional.select.rated.output": "정격출력",
@ -134,6 +135,8 @@
"modal.circuit.trestle.setting.power.conditional.select.max.connection": "최대접속매수", "modal.circuit.trestle.setting.power.conditional.select.max.connection": "최대접속매수",
"modal.circuit.trestle.setting.power.conditional.select.max.overload": "과적최대매수", "modal.circuit.trestle.setting.power.conditional.select.max.overload": "과적최대매수",
"modal.circuit.trestle.setting.power.conditional.select.output.current": "출력전류", "modal.circuit.trestle.setting.power.conditional.select.output.current": "출력전류",
"modal.circuit.trestle.setting.power.conditional.select.check1": "동일경사 동일 방면의 면적인 경우, 같은 면으로서 회로를 나눈다.",
"modal.circuit.trestle.setting.power.conditional.select.check2": "MAX 접속(과적)으로 회로를 나눈다.",
"modal.circuit.trestle.setting.circuit.allocation": "회로 할당", "modal.circuit.trestle.setting.circuit.allocation": "회로 할당",
"modal.circuit.trestle.setting.circuit.allocation.auto": "자동 회로 할당", "modal.circuit.trestle.setting.circuit.allocation.auto": "자동 회로 할당",
"modal.circuit.trestle.setting.circuit.allocation.passivity": "수동 회로 할당", "modal.circuit.trestle.setting.circuit.allocation.passivity": "수동 회로 할당",
@ -144,6 +147,12 @@
"modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "모든 회로번호 초기화", "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "모든 회로번호 초기화",
"modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "번호 확정", "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "번호 확정",
"modal.circuit.trestle.setting.step.up.allocation": "승압 설정", "modal.circuit.trestle.setting.step.up.allocation": "승압 설정",
"modal.circuit.trestle.setting.step.up.allocation.serial.amount": "직렬매수",
"modal.circuit.trestle.setting.step.up.allocation.total.amount": "총 회로수",
"modal.circuit.trestle.setting.step.up.allocation.connected": "연결함",
"modal.circuit.trestle.setting.step.up.allocation.circuit.amount": "승압회로수",
"modal.circuit.trestle.setting.step.up.allocation.option": "승압옵션",
"modal.circuit.trestle.setting.step.up.allocation.select.monitor": "모니터 선택",
"plan.menu.module.circuit.setting.plan.orientation": "도면 방위 적용", "plan.menu.module.circuit.setting.plan.orientation": "도면 방위 적용",
"plan.menu.estimate": "견적서", "plan.menu.estimate": "견적서",
"plan.menu.estimate.roof.alloc": "지붕면 할당", "plan.menu.estimate.roof.alloc": "지붕면 할당",
@ -480,6 +489,19 @@
"commons.east": "동", "commons.east": "동",
"commons.south": "남", "commons.south": "남",
"commons.north": "북", "commons.north": "북",
"font.style.normal": "보통",
"font.style.italic": "기울임꼴",
"font.style.bold": "굵게",
"font.style.bold.italic": "굵은 기울임꼴",
"color.black": "검정색",
"color.red": "빨강색",
"color.blue": "파랑색",
"color.gray": "회색",
"color.yellow": "황색",
"color.green": "녹색",
"color.pink": "분홍색",
"color.gold": "황금색",
"color.darkblue": "남색",
"site.name": "Q.CAST III", "site.name": "Q.CAST III",
"site.sub_name": "태양광 발전 시스템 도면관리 사이트", "site.sub_name": "태양광 발전 시스템 도면관리 사이트",
"board.notice.title": "공지사항", "board.notice.title": "공지사항",
@ -820,7 +842,10 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)", "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
"estimate.detail.header.showPrice": "가격표시", "estimate.detail.header.showPrice": "가격표시",
"estimate.detail.showPrice.btn1": "Pricing", "estimate.detail.showPrice.btn1": "Pricing",
"estimate.detail.showPrice.description": "클릭하여 제품 특이사항 확인", "estimate.detail.showPrice.description1": "제품 가격 OPEN",
"estimate.detail.showPrice.description2": "추가, 변경 자재",
"estimate.detail.showPrice.description3": "첨부필수",
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
"estimate.detail.showPrice.btn2": "제품추가", "estimate.detail.showPrice.btn2": "제품추가",
"estimate.detail.showPrice.btn3": "제품삭제" "estimate.detail.showPrice.btn3": "제품삭제"
} }

View File

@ -7,3 +7,9 @@ export const floorPlanObjectState = atom({
}, },
dangerouslyAllowMutability: true, dangerouslyAllowMutability: true,
}) })
export const estimateState = atom({
key: `estimateState`,
default: {},
dangerouslyAllowMutability: true,
})

View File

@ -7,7 +7,8 @@ import { atom } from 'recoil'
export const popupState = atom({ export const popupState = atom({
key: 'popupState', key: 'popupState',
default: { default: {
children: [], config: [],
other: [],
}, },
dangerouslyAllowMutability: true, dangerouslyAllowMutability: true,
}) })

View File

@ -5,7 +5,7 @@ export const settingModalFirstOptionsState = atom({
default: { default: {
option1: [ option1: [
{ id: 1, column: 'allocDisplay', name: 'modal.canvas.setting.first.option.alloc', selected: false }, { id: 1, column: 'allocDisplay', name: 'modal.canvas.setting.first.option.alloc', selected: false },
{ id: 2, column: 'outlineDisplay', name: 'modal.canvas.setting.first.option.outline', selected: false }, { id: 2, column: 'outlineDisplay', name: 'modal.canvas.setting.first.option.outline', selected: true },
{ id: 3, column: 'gridDisplay', name: 'modal.canvas.setting.first.option.grid', selected: false }, { id: 3, column: 'gridDisplay', name: 'modal.canvas.setting.first.option.grid', selected: false },
{ id: 4, column: 'lineDisplay', name: 'modal.canvas.setting.first.option.roof.line', selected: false }, { id: 4, column: 'lineDisplay', name: 'modal.canvas.setting.first.option.roof.line', selected: false },
{ id: 5, column: 'wordDisplay', name: 'modal.canvas.setting.first.option.word', selected: false }, { id: 5, column: 'wordDisplay', name: 'modal.canvas.setting.first.option.word', selected: false },

View File

@ -26,14 +26,14 @@
min-width: 1280px; min-width: 1280px;
padding-bottom: 0; padding-bottom: 0;
background-color: #383838; background-color: #383838;
transition: padding .17s ease-in-out; transition: padding 0.17s ease-in-out;
z-index: 999; z-index: 999;
.canvas-menu-inner { .canvas-menu-inner {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 40px 0 20px; padding: 0 40px 0 20px;
background-color: #2C2C2C; background-color: #2c2c2c;
height: 46.8px; height: 46.8px;
z-index: 999; z-index: 999;
.canvas-menu-list { .canvas-menu-list {
@ -53,7 +53,7 @@
font-weight: 600; font-weight: 600;
padding: 15px 20px; padding: 15px 20px;
opacity: 0.55; opacity: 0.55;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
.menu-icon { .menu-icon {
display: block; display: block;
width: 14px; width: 14px;
@ -62,13 +62,27 @@
background-position: center; background-position: center;
background-size: contain; background-size: contain;
margin-right: 10px; margin-right: 10px;
&.con00{background-image: url(/static/images/canvas/menu_icon00.svg);} &.con00 {
&.con01{background-image: url(/static/images/canvas/menu_icon01.svg);} background-image: url(/static/images/canvas/menu_icon00.svg);
&.con02{background-image: url(/static/images/canvas/menu_icon02.svg);} }
&.con03{background-image: url(/static/images/canvas/menu_icon03.svg);} &.con01 {
&.con04{background-image: url(/static/images/canvas/menu_icon04.svg);} background-image: url(/static/images/canvas/menu_icon01.svg);
&.con05{background-image: url(/static/images/canvas/menu_icon05.svg);} }
&.con06{background-image: url(/static/images/canvas/menu_icon06.svg);} &.con02 {
background-image: url(/static/images/canvas/menu_icon02.svg);
}
&.con03 {
background-image: url(/static/images/canvas/menu_icon03.svg);
}
&.con04 {
background-image: url(/static/images/canvas/menu_icon04.svg);
}
&.con05 {
background-image: url(/static/images/canvas/menu_icon05.svg);
}
&.con06 {
background-image: url(/static/images/canvas/menu_icon06.svg);
}
} }
} }
&.active { &.active {
@ -100,25 +114,43 @@
width: 30px; width: 30px;
height: 30px; height: 30px;
border-radius: 2px; border-radius: 2px;
background-color: #3D3D3D; background-color: #3d3d3d;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 15px 15px; background-size: 15px 15px;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
&.btn01{background-image: url(../../public/static/images/canvas/side_icon03.svg);} &.btn01 {
&.btn02{background-image: url(../../public/static/images/canvas/side_icon02.svg);} background-image: url(../../public/static/images/canvas/side_icon03.svg);
&.btn03{background-image: url(../../public/static/images/canvas/side_icon01.svg);} }
&.btn04{background-image: url(../../public/static/images/canvas/side_icon04.svg);} &.btn02 {
&.btn05{background-image: url(../../public/static/images/canvas/side_icon05.svg);} background-image: url(../../public/static/images/canvas/side_icon02.svg);
&.btn06{background-image: url(../../public/static/images/canvas/side_icon06.svg);} }
&.btn07{background-image: url(../../public/static/images/canvas/side_icon07.svg);} &.btn03 {
&.btn08{background-image: url(../../public/static/images/canvas/side_icon08.svg);} background-image: url(../../public/static/images/canvas/side_icon01.svg);
&.btn09{background-image: url(../../public/static/images/canvas/side_icon09.svg);} }
&.btn04 {
background-image: url(../../public/static/images/canvas/side_icon04.svg);
}
&.btn05 {
background-image: url(../../public/static/images/canvas/side_icon05.svg);
}
&.btn06 {
background-image: url(../../public/static/images/canvas/side_icon06.svg);
}
&.btn07 {
background-image: url(../../public/static/images/canvas/side_icon07.svg);
}
&.btn08 {
background-image: url(../../public/static/images/canvas/side_icon08.svg);
}
&.btn09 {
background-image: url(../../public/static/images/canvas/side_icon09.svg);
}
&:hover { &:hover {
background-color: #1083E3; background-color: #1083e3;
} }
&.active { &.active {
background-color: #1083E3; background-color: #1083e3;
} }
} }
} }
@ -134,10 +166,18 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: contain; background-size: contain;
&.ico01{background-image: url(../../public/static/images/canvas/ico-flx01.svg);} &.ico01 {
&.ico02{background-image: url(../../public/static/images/canvas/ico-flx02.svg);} background-image: url(../../public/static/images/canvas/ico-flx01.svg);
&.ico03{background-image: url(../../public/static/images/canvas/ico-flx03.svg);} }
&.ico04{background-image: url(../../public/static/images/canvas/ico-flx04.svg);} &.ico02 {
background-image: url(../../public/static/images/canvas/ico-flx02.svg);
}
&.ico03 {
background-image: url(../../public/static/images/canvas/ico-flx03.svg);
}
&.ico04 {
background-image: url(../../public/static/images/canvas/ico-flx04.svg);
}
} }
.name { .name {
font-size: 12px; font-size: 12px;
@ -167,16 +207,16 @@
button { button {
margin-left: auto; margin-left: auto;
height: 100%; height: 100%;
background-color: #4B4B4B; background-color: #4b4b4b;
font-size: 13px; font-size: 13px;
font-weight: 400; font-weight: 400;
color: #fff; color: #fff;
padding: 0 7.5px; padding: 0 7.5px;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
} }
&.on { &.on {
button { button {
background-color: #1083E3; background-color: #1083e3;
} }
} }
} }
@ -185,7 +225,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
background-color: #3D3D3D; background-color: #3d3d3d;
border-radius: 2px; border-radius: 2px;
width: 100px; width: 100px;
height: 30px; height: 30px;
@ -218,7 +258,7 @@
background-color: #383838; background-color: #383838;
width: 100%; width: 100%;
height: 50px; height: 50px;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
.canvas-depth2-inner { .canvas-depth2-inner {
display: flex; display: flex;
align-items: center; align-items: center;
@ -270,7 +310,7 @@
align-items: center; align-items: center;
margin-right: 34px; margin-right: 34px;
height: 100%; height: 100%;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
button { button {
position: relative; position: relative;
font-size: 12px; font-size: 12px;
@ -310,7 +350,7 @@
// canvas-layout // canvas-layout
.canvas-content { .canvas-content {
padding-top: 46.8px; padding-top: 46.8px;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
.canvas-frame { .canvas-frame {
height: calc(100vh - 129.3px); height: calc(100vh - 129.3px);
} }
@ -328,11 +368,11 @@
top: 92.8px; top: 92.8px;
left: 0; left: 0;
display: flex; display: flex;
background-color: #1C1C1C; background-color: #1c1c1c;
border-top: 1px solid #000; border-top: 1px solid #000;
width: 100%; width: 100%;
min-width: 1280px; min-width: 1280px;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
z-index: 99; z-index: 99;
&.active { &.active {
top: calc(92.8px + 50px); top: calc(92.8px + 50px);
@ -348,14 +388,14 @@
padding: 9.6px 20px; padding: 9.6px 20px;
border-right: 1px solid #000; border-right: 1px solid #000;
min-width: 0; min-width: 0;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
span { span {
display: flex; display: flex;
align-items: center; align-items: center;
width: 100%; width: 100%;
font-size: 12px; font-size: 12px;
font-family: 'Pretendard', sans-serif; font-family: 'Pretendard', sans-serif;
color: #AAA; color: #aaa;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
@ -393,9 +433,9 @@
justify-content: center; justify-content: center;
width: 45px; width: 45px;
padding: 13.5px 0; padding: 13.5px 0;
background-color: #1C1C1C; background-color: #1c1c1c;
border-right: 1px solid #000; border-right: 1px solid #000;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
span { span {
display: block; display: block;
width: 9px; width: 9px;
@ -413,9 +453,9 @@
.canvas-frame { .canvas-frame {
position: relative; position: relative;
// height: calc(100% - 36.5px); // height: calc(100% - 36.5px);
background-color: #F4F4F7; background-color: #f4f4f7;
overflow: auto; overflow: auto;
transition: all .17s ease-in-out; transition: all 0.17s ease-in-out;
// &::-webkit-scrollbar { // &::-webkit-scrollbar {
// width: 10px; // width: 10px;
// height: 10px; // height: 10px;
@ -450,7 +490,7 @@
min-width: 1280px; min-width: 1280px;
height: 46px; height: 46px;
border-bottom: 1px solid #000; border-bottom: 1px solid #000;
background: #2C2C2C; background: #2c2c2c;
z-index: 999; z-index: 999;
.sub-header-inner { .sub-header-inner {
display: flex; display: flex;
@ -473,7 +513,9 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
&.drawing{background-image: url(../../public/static/images/main/drawing_icon.svg);} &.drawing {
background-image: url(../../public/static/images/main/drawing_icon.svg);
}
} }
} }
&:after { &:after {
@ -484,7 +526,7 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 16px; height: 16px;
background-color: #D9D9D9; background-color: #d9d9d9;
} }
&:first-child { &:first-child {
padding-left: 0; padding-left: 0;
@ -514,7 +556,7 @@
span { span {
display: flex; display: flex;
font-size: 12px; font-size: 12px;
color: #AAA; color: #aaa;
font-weight: normal; font-weight: normal;
cursor: default; cursor: default;
} }
@ -572,8 +614,8 @@
.sub-table-box { .sub-table-box {
padding: 20px; padding: 20px;
border-radius: 6px; border-radius: 6px;
border: 1px solid #E9EAED; border: 1px solid #e9eaed;
background: #FFF; background: #fff;
box-shadow: 0px 3px 30px 0px rgba(0, 0, 0, 0.02); box-shadow: 0px 3px 30px 0px rgba(0, 0, 0, 0.02);
.table-box-title-wrap { .table-box-title-wrap {
display: flex; display: flex;
@ -596,7 +638,7 @@
position: relative; position: relative;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: #1083E3; color: #1083e3;
padding-left: 10px; padding-left: 10px;
&::before { &::before {
content: ''; content: '';
@ -606,7 +648,7 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 11px; height: 11px;
background-color: #D9D9D9; background-color: #d9d9d9;
} }
} }
.option { .option {
@ -627,7 +669,7 @@
span { span {
font-weight: 600; font-weight: 600;
&.red { &.red {
color: #E23D70; color: #e23d70;
} }
} }
&:after { &:after {
@ -638,10 +680,17 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 11px; height: 11px;
background-color: #D9D9D9; background-color: #d9d9d9;
}
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
&::after {
display: none;
}
} }
&:first-child{padding-left: 0;}
&:last-child{padding-right: 0;&::after{display: none;}}
} }
} }
} }
@ -734,7 +783,7 @@
width: 105px; width: 105px;
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
background-color: #F4F4F7; background-color: #f4f4f7;
border-radius: 100px; border-radius: 100px;
text-align: center; text-align: center;
font-size: 13px; font-size: 13px;
@ -749,12 +798,12 @@
&.blue { &.blue {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
color: #1083E3; color: #1083e3;
} }
&.red { &.red {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
color: #D72A2A; color: #d72a2a;
} }
} }
} }
@ -768,11 +817,11 @@
padding: 10px; padding: 10px;
.btn-area { .btn-area {
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: 1px solid #ECF0F4; border-bottom: 1px solid #ecf0f4;
.file-upload { .file-upload {
display: inline-block; display: inline-block;
height: 30px; height: 30px;
background-color: #94A0AD; background-color: #94a0ad;
padding: 0 10px; padding: 0 10px;
border-radius: 2px; border-radius: 2px;
font-size: 13px; font-size: 13px;
@ -780,9 +829,9 @@
color: #fff; color: #fff;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
transition: background .15s ease-in-out; transition: background 0.15s ease-in-out;
&:hover { &:hover {
background-color: #607F9A; background-color: #607f9a;
} }
} }
} }
@ -806,7 +855,7 @@
span { span {
position: relative; position: relative;
font-size: 13px; font-size: 13px;
color: #45576F; color: #45576f;
font-weight: 400; font-weight: 400;
white-space: nowrap; white-space: nowrap;
padding-right: 55px; padding-right: 55px;
@ -828,30 +877,57 @@
} }
} }
.estimate-arr-btn {
display: block;
width: 20px;
height: 20px;
background-color: #94a0ad;
border: 1px solid #94a0ad;
background-position: center;
background-repeat: no-repeat;
background-image: url(../../public/static/images/canvas/estiment_arr.svg);
background-size: 11px 7px;
border-radius: 2px;
&.up {
rotate: 180deg;
}
&.on {
background-color: #fff;
border-color: #c2d0dd;
background-image: url(../../public/static/images/canvas/estiment_arr_color.svg);
}
}
.estimate-check-wrap {
.estimate-check-inner {
display: block;
}
&.hide {
border-bottom: 1px solid #ecf0f4;
margin-bottom: 15px;
.estimate-check-inner {
display: none;
}
}
}
.special-note-check-wrap { .special-note-check-wrap {
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
border: 1px solid #ECF0F4;
border-radius: 3px; border-radius: 3px;
margin-bottom: 30px; margin-bottom: 30px;
.special-note-check-item { .special-note-check-item {
padding: 14px 10px; padding: 14px 10px;
border-right: 1px solid #ECF0F4; border: 1px solid #ecf0f4;
border-top: 1px solid #ECF0F4; margin-top: -1px;
&:nth-child(5n){ margin-right: -1px;
border-right: none;
}
&:nth-child(-n+5){
border-top: none;
}
&.act { &.act {
background-color: #F7F9FA; background-color: #f7f9fa;
} }
} }
} }
.calculation-estimate { .calculation-estimate {
border: 1px solid #ECF0F4; border: 1px solid #ecf0f4;
border-radius: 3px; border-radius: 3px;
padding: 24px; padding: 24px;
max-height: 350px; max-height: 350px;
@ -865,13 +941,13 @@
dt { dt {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #1083E3; color: #1083e3;
margin-bottom: 15px; margin-bottom: 15px;
} }
dd { dd {
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
color: #45576F; color: #45576f;
margin-bottom: 8px; margin-bottom: 8px;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
@ -903,7 +979,7 @@
.product-price-tit { .product-price-tit {
font-size: 13px; font-size: 13px;
font-weight: 400; font-weight: 400;
color: #45576F; color: #45576f;
margin-right: 10px; margin-right: 10px;
} }
.select-wrap { .select-wrap {
@ -941,7 +1017,7 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 12px; height: 12px;
background-color: #D9D9D9; background-color: #d9d9d9;
} }
&:first-child { &:first-child {
padding-left: 0; padding-left: 0;
@ -953,7 +1029,7 @@
padding-right: 0; padding-right: 0;
} }
&.item01 { &.item01 {
color: #3BBB48; color: #3bbb48;
span { span {
background-image: url(../../public/static/images/sub/open_ico.svg); background-image: url(../../public/static/images/sub/open_ico.svg);
} }
@ -965,13 +1041,13 @@
} }
} }
&.item03 { &.item03 {
color: #0191C9; color: #0191c9;
span { span {
background-image: url(../../public/static/images/sub/attachment_ico.svg); background-image: url(../../public/static/images/sub/attachment_ico.svg);
} }
} }
&.item04 { &.item04 {
color: #F16A6A; color: #f16a6a;
span { span {
background-image: url(../../public/static/images/sub/click_check_ico.svg); background-image: url(../../public/static/images/sub/click_check_ico.svg);
} }
@ -1033,23 +1109,23 @@
table { table {
table-layout: fixed; table-layout: fixed;
border-collapse: collapse; border-collapse: collapse;
border: 1px solid #ECF0F4; border: 1px solid #ecf0f4;
border-radius: 4px; border-radius: 4px;
thead { thead {
th { th {
padding: 4.5px 0; padding: 4.5px 0;
border-bottom: 1px solid #ECF0F4; border-bottom: 1px solid #ecf0f4;
text-align: center; text-align: center;
font-size: 13px; font-size: 13px;
color: #45576F; color: #45576f;
font-weight: 500; font-weight: 500;
background-color: #F8F9FA; background-color: #f8f9fa;
} }
} }
tbody { tbody {
td { td {
font-size: 13px; font-size: 13px;
color: #45576F; color: #45576f;
text-align: center; text-align: center;
padding: 4.5px 0; padding: 4.5px 0;
} }
@ -1063,13 +1139,13 @@
.simulation-tit-wrap { .simulation-tit-wrap {
flex: none; flex: none;
padding-right: 40px; padding-right: 40px;
border-right: 1px solid #EEEEEE; border-right: 1px solid #eeeeee;
span { span {
display: block; display: block;
position: relative; position: relative;
padding-left: 60px; padding-left: 60px;
font-size: 15px; font-size: 15px;
color: #14324F; color: #14324f;
font-weight: 600; font-weight: 600;
&::before { &::before {
content: ''; content: '';
@ -1097,7 +1173,7 @@
} }
dd { dd {
font-size: 12px; font-size: 12px;
color: #45576F; color: #45576f;
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
} }
@ -1105,7 +1181,8 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
ul, ol{ ul,
ol {
list-style: unset; list-style: unset;
} }
} }
@ -1114,10 +1191,10 @@
.module-total { .module-total {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #F8F9FA; background-color: #f8f9fa;
padding: 9px 0; padding: 9px 0;
margin-right: 4px; margin-right: 4px;
border: 1px solid #ECF0F4; border: 1px solid #ecf0f4;
border-top: none; border-top: none;
.total-title { .total-title {
flex: 1; flex: 1;
@ -1140,7 +1217,7 @@
.information-help-wrap { .information-help-wrap {
display: flex; display: flex;
padding: 24px; padding: 24px;
background-color: #F4F4F4; background-color: #f4f4f4;
border-radius: 4px; border-radius: 4px;
margin-bottom: 15px; margin-bottom: 15px;
.information-help-tit-wrap { .information-help-tit-wrap {
@ -1148,7 +1225,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
padding-right: 40px; padding-right: 40px;
border-right: 1px solid #E0E0E3; border-right: 1px solid #e0e0e3;
.help-tit-icon { .help-tit-icon {
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -1160,7 +1237,7 @@
.help-tit { .help-tit {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #45576F; color: #45576f;
} }
} }
.information-help-guide { .information-help-guide {
@ -1169,7 +1246,7 @@
display: block; display: block;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
color: #45576F; color: #45576f;
margin-bottom: 7px; margin-bottom: 7px;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
@ -1183,7 +1260,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 10px 0 30px 0; padding: 10px 0 30px 0;
border-bottom: 1px solid #E5E5E5; border-bottom: 1px solid #e5e5e5;
margin-bottom: 24px; margin-bottom: 24px;
.community-search-box { .community-search-box {
position: relative; position: relative;
@ -1202,7 +1279,7 @@
font-weight: 400; font-weight: 400;
color: #101010; color: #101010;
&::placeholder { &::placeholder {
color: #C8C8C8; color: #c8c8c8;
} }
} }
.community-search-ico { .community-search-ico {
@ -1221,10 +1298,10 @@
.community-search-keyword { .community-search-keyword {
font-size: 13px; font-size: 13px;
font-weight: 400; font-weight: 400;
color: #45576F; color: #45576f;
span { span {
font-weight: 600; font-weight: 600;
color: #F16A6A; color: #f16a6a;
} }
} }
} }
@ -1239,15 +1316,15 @@
align-items: center; align-items: center;
padding: 24px; padding: 24px;
border-radius: 4px; border-radius: 4px;
border: 1px solid #E5E5E5; border: 1px solid #e5e5e5;
background: #FFF; background: #fff;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
.file-item-info { .file-item-info {
.item-num { .item-num {
display: inline-block; display: inline-block;
padding: 6px 17.5px; padding: 6px 17.5px;
border-radius: 60px; border-radius: 60px;
background-color: #F4F4F7; background-color: #f4f4f7;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #101010; color: #101010;
@ -1279,7 +1356,7 @@
} }
} }
&:hover { &:hover {
background-color: #F4F4F7; background-color: #f4f4f7;
} }
} }
} }
@ -1292,7 +1369,7 @@
height: 148px; height: 148px;
padding: 24px; padding: 24px;
border-radius: 4px; border-radius: 4px;
border: 1px solid #E5E5E5; border: 1px solid #e5e5e5;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: #344356; color: #344356;
@ -1304,14 +1381,15 @@
align-items: center; align-items: center;
width: 200px; width: 200px;
height: 30px; height: 30px;
background-color: #FAFAFA; background-color: #fafafa;
border: 1px solid #EEE; border: 1px solid #eee;
padding: 0 10px; padding: 0 10px;
input { input {
font-size: 13px; font-size: 13px;
font-weight: 400; font-weight: 400;
color: #999999; color: #999999;
padding: 0; padding: 0;
width: 100%;
height: 100%; height: 100%;
flex: 1; flex: 1;
background-color: inherit; background-color: inherit;
@ -1428,5 +1506,4 @@
} }
} }
} }
} }

View File

@ -239,6 +239,7 @@ footer{
nav{ nav{
.nav-list{ .nav-list{
.nav-item{ .nav-item{
a,
button{ button{
font-size: 13px; font-size: 13px;
} }

View File

@ -37,9 +37,10 @@
top: 50%; top: 50%;
left: 0; left: 0;
transform: translateY(-50%); transform: translateY(-50%);
width: 20px; width: 22px;
height: 20px; height: 22px;
background: url(../../public/static/images/main/id_icon.svg)no-repeat center; background: url(../../public/static/images/main/id_icon.svg)no-repeat center;
background-size: cover;
} }
} }
.store-arr{ .store-arr{

View File

@ -5,12 +5,24 @@ $pop-normal-size: 12px;
$alert-color: #101010; $alert-color: #101010;
@keyframes mountpop { @keyframes mountpop {
from{opacity: 0; scale: 0.95;} from {
to{opacity: 1; scale: 1;} opacity: 0;
scale: 0.95;
}
to {
opacity: 1;
scale: 1;
}
} }
@keyframes unmountpop { @keyframes unmountpop {
from{opacity: 1; scale: 1;} from {
to{opacity: 0; scale: 0.95;} opacity: 1;
scale: 1;
}
to {
opacity: 0;
scale: 0.95;
}
} }
.normal-font { .normal-font {
@ -82,10 +94,10 @@ $alert-color: #101010;
width: 800px; width: 800px;
} }
&.mount { &.mount {
animation: mountpop .17s ease-in-out forwards; animation: mountpop 0.17s ease-in-out forwards;
} }
&.unmount { &.unmount {
animation: unmountpop .17s ease-in-out forwards; animation: unmountpop 0.17s ease-in-out forwards;
} }
&.alert { &.alert {
position: absolute; position: absolute;
@ -190,7 +202,7 @@ $alert-color: #101010;
} }
} }
.outer-line-wrap { .outer-line-wrap {
border-top: 1px solid #3C3C3C; border-top: 1px solid #3c3c3c;
margin-top: 10px; margin-top: 10px;
padding-top: 15px; padding-top: 15px;
margin-bottom: 15px; margin-bottom: 15px;
@ -212,7 +224,7 @@ $alert-color: #101010;
.adsorption-point { .adsorption-point {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #3A3A3A; background-color: #3a3a3a;
border-radius: 3px; border-radius: 3px;
padding-left: 11px; padding-left: 11px;
overflow: hidden; overflow: hidden;
@ -233,7 +245,7 @@ $alert-color: #101010;
&.act { &.act {
i { i {
color: $pop-color; color: $pop-color;
background-color: #1083E3; background-color: #1083e3;
} }
} }
} }
@ -253,7 +265,7 @@ $alert-color: #101010;
display: flex; display: flex;
align-items: center; align-items: center;
background-color: transparent; background-color: transparent;
border: 1px solid #3D3D3D; border: 1px solid #3d3d3d;
border-radius: 2px; border-radius: 2px;
padding: 15px 10px; padding: 15px 10px;
gap: 20px; gap: 20px;
@ -280,7 +292,9 @@ $alert-color: #101010;
} }
} }
.select-form { .select-form {
.sort-select{width: 100%;} .sort-select {
width: 100%;
}
} }
.grid-select { .grid-select {
flex: 1; flex: 1;
@ -325,7 +339,6 @@ $alert-color: #101010;
color: $pop-color; color: $pop-color;
font-weight: $pop-normal-weight; font-weight: $pop-normal-weight;
padding-bottom: 15px; padding-bottom: 15px;
} }
.grid-direction { .grid-direction {
display: flex; display: flex;
@ -342,11 +355,17 @@ $alert-color: #101010;
background-position: center; background-position: center;
background-size: 16px 15px; background-size: 16px 15px;
border-radius: 50%; border-radius: 50%;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
opacity: 0.6; opacity: 0.6;
&.down{transform: rotate(180deg);} &.down {
&.left{transform: rotate(-90deg);} transform: rotate(180deg);
&.right{transform: rotate(90deg);} }
&.left {
transform: rotate(-90deg);
}
&.right {
transform: rotate(90deg);
}
&:hover, &:hover,
&.act { &.act {
opacity: 1; opacity: 1;
@ -391,19 +410,22 @@ $alert-color: #101010;
table-layout: fixed; table-layout: fixed;
tr { tr {
th { th {
display: flex;
align-items: center;
font-size: $pop-normal-size; font-size: $pop-normal-size;
color: $pop-color; color: $pop-color;
font-weight: $pop-bold-weight; font-weight: $pop-bold-weight;
padding: 18px 0; padding: 18px 0;
border-bottom: 1px solid #424242; border-bottom: 1px solid #424242;
vertical-align: middle;
.tip-wrap {
display: flex;
align-items: center;
}
} }
td { td {
font-size: $pop-normal-size; font-size: $pop-normal-size;
color: $pop-color; color: $pop-color;
border-bottom: 1px solid #424242; border-bottom: 1px solid #424242;
padding-left: 20px; padding: 18px 0 18px 20px;
vertical-align: middle; vertical-align: middle;
.flex-box { .flex-box {
display: flex; display: flex;
@ -430,10 +452,11 @@ $alert-color: #101010;
} }
&.light { &.light {
padding: 0; padding: 0;
th,td{ th,
td {
color: $alert-color; color: $alert-color;
border-bottom: none; border-bottom: none;
border-top: 1px solid #EFEFEF; border-top: 1px solid #efefef;
} }
th { th {
padding: 14px 0; padding: 14px 0;
@ -494,7 +517,7 @@ $alert-color: #101010;
background-color: #fff; background-color: #fff;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
.img-edit { .img-edit {
width: 16px; width: 16px;
height: 16px; height: 16px;
@ -514,7 +537,6 @@ $alert-color: #101010;
margin-left: 10px; margin-left: 10px;
input { input {
flex: 1; flex: 1;
} }
.img-check { .img-check {
flex: none; flex: none;
@ -528,6 +550,27 @@ $alert-color: #101010;
} }
} }
.for-address {
input {
flex: 1;
}
.check-address {
flex: none;
width: 18px;
height: 18px;
margin-left: 5px;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
&.fail {
background-image: url(../../public/static/images/canvas/img_check_fail.svg);
}
&.success {
background-image: url(../../public/static/images/canvas/img_check_success.svg);
}
}
}
// 외벽선 그리기 // 외벽선 그리기
.outline-wrap { .outline-wrap {
padding: 24px 0; padding: 24px 0;
@ -604,7 +647,7 @@ $alert-color: #101010;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 50%; width: 50%;
background-color: #3D3D3D; background-color: #3d3d3d;
border-radius: 2px; border-radius: 2px;
} }
} }
@ -612,7 +655,7 @@ $alert-color: #101010;
// 외벽선 속성 설정 // 외벽선 속성 설정
.properties-guide { .properties-guide {
font-size: $pop-normal-size; font-size: $pop-normal-size;
color: #AAA; color: #aaa;
font-weight: $pop-normal-weight; font-weight: $pop-normal-weight;
margin-bottom: 14px; margin-bottom: 14px;
} }
@ -641,17 +684,17 @@ $alert-color: #101010;
color: #fff; color: #fff;
font-weight: 700; font-weight: 700;
border-radius: 2px; border-radius: 2px;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&.green { &.green {
background-color: #305941; background-color: #305941;
border: 1px solid #45CD7D; border: 1px solid #45cd7d;
&:hover { &:hover {
background-color: #3a6b4e; background-color: #3a6b4e;
} }
} }
&.blue { &.blue {
background-color: #2E5360; background-color: #2e5360;
border: 1px solid #3FBAE6; border: 1px solid #3fbae6;
&:hover { &:hover {
background-color: #365f6e; background-color: #365f6e;
} }
@ -673,8 +716,8 @@ $alert-color: #101010;
justify-content: center; justify-content: center;
width: 100%; width: 100%;
padding: 13px; padding: 13px;
background-color: #3D3D3D; background-color: #3d3d3d;
transition: background .15s ease-in-out; transition: background 0.15s ease-in-out;
img { img {
max-width: 100%; max-width: 100%;
} }
@ -685,13 +728,17 @@ $alert-color: #101010;
color: $pop-color; color: $pop-color;
margin-top: 10px; margin-top: 10px;
text-align: center; text-align: center;
transition: color .15s ease-in-out; transition: color 0.15s ease-in-out;
} }
.shape-menu-box { .shape-menu-box {
&.act, &.act,
&:hover { &:hover {
.shape-box{background-color: #008BFF;} .shape-box {
.shape-title{color: #008BFF;} background-color: #008bff;
}
.shape-title {
color: #008bff;
}
} }
} }
} }
@ -706,7 +753,7 @@ $alert-color: #101010;
} }
.discrimination-box { .discrimination-box {
padding: 16px 12px; padding: 16px 12px;
border: 1px solid #3D3D3D; border: 1px solid #3d3d3d;
border-radius: 2px; border-radius: 2px;
} }
@ -739,12 +786,12 @@ $alert-color: #101010;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 5px; padding: 5px;
background-color: #3D3D3D; background-color: #3d3d3d;
border: 1px solid #3D3D3D; border: 1px solid #3d3d3d;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
&.act { &.act {
border: 1px solid #ED0004; border: 1px solid #ed0004;
} }
} }
&:last-child { &:last-child {
@ -843,7 +890,7 @@ $alert-color: #101010;
border: 1px solid #646464; border: 1px solid #646464;
border-radius: 2px; border-radius: 2px;
padding: 0 10px; padding: 0 10px;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
i { i {
height: 15px; height: 15px;
display: block; display: block;
@ -851,7 +898,7 @@ $alert-color: #101010;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&.allocation01 { &.allocation01 {
background-image: url(../../public/static/images/canvas/allocation_icon01_white.svg); background-image: url(../../public/static/images/canvas/allocation_icon01_white.svg);
width: 15px; width: 15px;
@ -904,7 +951,9 @@ $alert-color: #101010;
height: 34px; height: 34px;
background-color: #373737; background-color: #373737;
border: 1px solid #676767; border: 1px solid #676767;
transition: background .15s ease-in-out, border .15s ease-in-out; transition:
background 0.15s ease-in-out,
border 0.15s ease-in-out;
.shape-box { .shape-box {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -916,8 +965,8 @@ $alert-color: #101010;
} }
&.act, &.act,
&:hover { &:hover {
border-color: #008BFF; border-color: #008bff;
background-color: #008BFF; background-color: #008bff;
} }
} }
} }
@ -932,18 +981,27 @@ $alert-color: #101010;
.library-btn { .library-btn {
width: 100%; width: 100%;
height: 34px; height: 34px;
border: 1px solid #6C6C6C; border: 1px solid #6c6c6c;
border-radius: 2px; border-radius: 2px;
background-color: #373737; background-color: #373737;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&.ico01{background-image: url(../../public/static/images/canvas/shape_labrary01.svg); background-size: 19px 18px;} &.ico01 {
&.ico02{background-image: url(../../public/static/images/canvas/shape_labrary02.svg); background-size: 15px 20px;} background-image: url(../../public/static/images/canvas/shape_labrary01.svg);
&.ico03{background-image: url(../../public/static/images/canvas/shape_labrary03.svg); background-size: 19px 16px;} background-size: 19px 18px;
}
&.ico02 {
background-image: url(../../public/static/images/canvas/shape_labrary02.svg);
background-size: 15px 20px;
}
&.ico03 {
background-image: url(../../public/static/images/canvas/shape_labrary03.svg);
background-size: 19px 16px;
}
&:hover { &:hover {
border-color: #1083E3; border-color: #1083e3;
background-color: #1083E3; background-color: #1083e3;
} }
} }
} }
@ -1029,11 +1087,27 @@ $alert-color: #101010;
position: absolute; position: absolute;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
color: #B1B1B1; color: #b1b1b1;
&.top{top: 0; left: 50%; transform: translateX(-50%);} &.top {
&.right{top: 50%; right: 0; transform: translateY(-50%);} top: 0;
&.bottom{bottom: 0; left: 50%; transform: translateX(-50%);} left: 50%;
&.left{top: 50%; left: 0; transform: translateY(-50%);} transform: translateX(-50%);
}
&.right {
top: 50%;
right: 0;
transform: translateY(-50%);
}
&.bottom {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
&.left {
top: 50%;
left: 0;
transform: translateY(-50%);
}
} }
.plane-btn { .plane-btn {
position: absolute; position: absolute;
@ -1045,11 +1119,27 @@ $alert-color: #101010;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
border-radius: 50%; border-radius: 50%;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&.up{top: 22px; left: 50%; transform: translateX(-50%);} &.up {
&.right{top: 50%; right: 32px; transform: translateY(-50%) rotate(90deg);} top: 22px;
&.down{bottom: 22px; left: 50%; transform: translateX(-50%) rotate(180deg);} left: 50%;
&.left{top: 50%; left: 32px; transform: translateY(-50%) rotate(270deg);} transform: translateX(-50%);
}
&.right {
top: 50%;
right: 32px;
transform: translateY(-50%) rotate(90deg);
}
&.down {
bottom: 22px;
left: 50%;
transform: translateX(-50%) rotate(180deg);
}
&.left {
top: 50%;
left: 32px;
transform: translateY(-50%) rotate(270deg);
}
&:hover, &:hover,
&.act { &.act {
background-color: #fff; background-color: #fff;
@ -1115,7 +1205,7 @@ $alert-color: #101010;
.warning { .warning {
font-size: $pop-normal-size; font-size: $pop-normal-size;
font-weight: $pop-normal-weight; font-weight: $pop-normal-weight;
color: #FFAFAF; color: #ffafaf;
} }
// 속성 변경 // 속성 변경
@ -1167,37 +1257,157 @@ $alert-color: #101010;
top: 12.5px; top: 12.5px;
left: 50%; left: 50%;
font-size: 11px; font-size: 11px;
color: #8B8B8B; color: #8b8b8b;
font-weight: 500; font-weight: 500;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-use-select: none; -ms-use-select: none;
user-select: none; user-select: none;
} }
&:nth-child(1) { transform: rotate(180deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(180deg);}} &:nth-child(1) {
&:nth-child(2) { transform: rotate(195deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(165deg);}} transform: rotate(180deg) translate(-50%, -50%);
&:nth-child(3) { transform: rotate(210deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(150deg);}} i {
&:nth-child(4) { transform: rotate(225deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(135deg);}} transform: translateX(-50%) rotate(180deg);
&:nth-child(5) { transform: rotate(240deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(120deg);}} }
&:nth-child(6) { transform: rotate(255deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(105deg);}} }
&:nth-child(7) { transform: rotate(270deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(90deg);}} &:nth-child(2) {
&:nth-child(8) { transform: rotate(285deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(75deg);}} transform: rotate(195deg) translate(-50%, -50%);
&:nth-child(9) { transform: rotate(300deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(60deg);}} i {
&:nth-child(10) { transform: rotate(315deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(45deg);}} transform: translateX(-50%) rotate(165deg);
&:nth-child(11) { transform: rotate(330deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(30deg);}} }
&:nth-child(12) { transform: rotate(345deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(15deg);}} }
&:nth-child(13) { transform: rotate(0deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(0deg);}} &:nth-child(3) {
&:nth-child(14) { transform: rotate(15deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-15deg);}} transform: rotate(210deg) translate(-50%, -50%);
&:nth-child(15) { transform: rotate(30deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-30deg);}} i {
&:nth-child(16) { transform: rotate(45deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-45deg);}} transform: translateX(-50%) rotate(150deg);
&:nth-child(17) { transform: rotate(60deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-60deg);}} }
&:nth-child(18) { transform: rotate(75deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-75deg);}} }
&:nth-child(19) { transform: rotate(90deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-90deg);}} &:nth-child(4) {
&:nth-child(20) { transform: rotate(105deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-105deg);}} transform: rotate(225deg) translate(-50%, -50%);
&:nth-child(21) { transform: rotate(120deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-120deg);}} i {
&:nth-child(22) { transform: rotate(135deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-135deg);}} transform: translateX(-50%) rotate(135deg);
&:nth-child(23) { transform: rotate(150deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-150deg);}} }
&:nth-child(24) { transform: rotate(165deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-165deg);}} }
&:nth-child(5) {
transform: rotate(240deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(120deg);
}
}
&:nth-child(6) {
transform: rotate(255deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(105deg);
}
}
&:nth-child(7) {
transform: rotate(270deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(90deg);
}
}
&:nth-child(8) {
transform: rotate(285deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(75deg);
}
}
&:nth-child(9) {
transform: rotate(300deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(60deg);
}
}
&:nth-child(10) {
transform: rotate(315deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(45deg);
}
}
&:nth-child(11) {
transform: rotate(330deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(30deg);
}
}
&:nth-child(12) {
transform: rotate(345deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(15deg);
}
}
&:nth-child(13) {
transform: rotate(0deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(0deg);
}
}
&:nth-child(14) {
transform: rotate(15deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-15deg);
}
}
&:nth-child(15) {
transform: rotate(30deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-30deg);
}
}
&:nth-child(16) {
transform: rotate(45deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-45deg);
}
}
&:nth-child(17) {
transform: rotate(60deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-60deg);
}
}
&:nth-child(18) {
transform: rotate(75deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-75deg);
}
}
&:nth-child(19) {
transform: rotate(90deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-90deg);
}
}
&:nth-child(20) {
transform: rotate(105deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-105deg);
}
}
&:nth-child(21) {
transform: rotate(120deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-120deg);
}
}
&:nth-child(22) {
transform: rotate(135deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-135deg);
}
}
&:nth-child(23) {
transform: rotate(150deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-150deg);
}
}
&:nth-child(24) {
transform: rotate(165deg) translate(-50%, -50%);
i {
transform: translateX(-50%) rotate(-165deg);
}
}
&.act { &.act {
&::after { &::after {
content: ''; content: '';
@ -1232,7 +1442,6 @@ $alert-color: #101010;
} }
} }
// 지붕모듈선택 // 지붕모듈선택
.roof-module-tab { .roof-module-tab {
display: flex; display: flex;
@ -1247,13 +1456,13 @@ $alert-color: #101010;
border-radius: 2px; border-radius: 2px;
background-color: transparent; background-color: transparent;
font-size: 12px; font-size: 12px;
color: #AAA; color: #aaa;
text-align: center; text-align: center;
cursor: default; cursor: default;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&.act { &.act {
background-color: #1083E3; background-color: #1083e3;
border: 1px solid #1083E3; border: 1px solid #1083e3;
color: #fff; color: #fff;
font-weight: 500; font-weight: 500;
} }
@ -1266,7 +1475,7 @@ $alert-color: #101010;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-image: url(../../public/static/images/canvas/module_tab_arr.svg); background-image: url(../../public/static/images/canvas/module_tab_arr.svg);
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&.act { &.act {
background-image: url(../../public/static/images/canvas/module_tab_arr_white.svg); background-image: url(../../public/static/images/canvas/module_tab_arr_white.svg);
} }
@ -1292,14 +1501,16 @@ $alert-color: #101010;
transform: translateX(-50%); transform: translateX(-50%);
width: 1px; width: 1px;
height: 6px; height: 6px;
background-color: #8B8B8B; background-color: #8b8b8b;
} }
} }
i { i {
top: 32px; top: 32px;
} }
&.act { &.act {
i{color: #8B8B8B;} i {
color: #8b8b8b;
}
} }
} }
} }
@ -1331,7 +1542,7 @@ $alert-color: #101010;
height: 30px; height: 30px;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
transition: all .15s ease-in-out; transition: all 0.15s ease-in-out;
&:first-child { &:first-child {
border-left: 1px solid #505050; border-left: 1px solid #505050;
} }
@ -1345,7 +1556,7 @@ $alert-color: #101010;
.module-table-box { .module-table-box {
flex: 1; flex: 1;
background-color: #3D3D3D; background-color: #3d3d3d;
border-radius: 2px; border-radius: 2px;
.module-table-inner { .module-table-inner {
padding: 10px; padding: 10px;
@ -1358,7 +1569,7 @@ $alert-color: #101010;
padding: 10px 0; padding: 10px 0;
font-size: 12px; font-size: 12px;
color: #fff; color: #fff;
border-bottom: 1px solid #4D4D4D; border-bottom: 1px solid #4d4d4d;
} }
.eaves-keraba-table { .eaves-keraba-table {
width: 100%; width: 100%;
@ -1381,7 +1592,7 @@ $alert-color: #101010;
.warning-guide { .warning-guide {
padding: 20px; padding: 20px;
.warning { .warning {
color: #FFCACA; color: #ffcaca;
max-height: 55px; max-height: 55px;
overflow-y: auto; overflow-y: auto;
padding-right: 30px; padding-right: 30px;
@ -1390,7 +1601,7 @@ $alert-color: #101010;
background-color: transparent; background-color: transparent;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background-color: #D9D9D9; background-color: #d9d9d9;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background-color: transparent; background-color: transparent;
@ -1401,7 +1612,7 @@ $alert-color: #101010;
.module-self-table { .module-self-table {
display: table; display: table;
border-top: 1px solid #4D4D4D; border-top: 1px solid #4d4d4d;
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
.self-table-item { .self-table-item {
@ -1410,7 +1621,7 @@ $alert-color: #101010;
.self-item-th { .self-item-th {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
border-bottom: 1px solid #4D4D4D; border-bottom: 1px solid #4d4d4d;
} }
.self-item-th { .self-item-th {
width: 60px; width: 60px;
@ -1438,7 +1649,7 @@ $alert-color: #101010;
.hexagonal-wrap { .hexagonal-wrap {
.hexagonal-item { .hexagonal-item {
padding: 15px 0; padding: 15px 0;
border-bottom: 1px solid #4D4D4D; border-bottom: 1px solid #4d4d4d;
&:first-child { &:first-child {
padding-top: 0; padding-top: 0;
} }
@ -1476,7 +1687,7 @@ $alert-color: #101010;
background-color: transparent; background-color: transparent;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background-color: #D9D9D9; background-color: #d9d9d9;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background-color: transparent; background-color: transparent;
@ -1494,7 +1705,7 @@ $alert-color: #101010;
gap: 5px; gap: 5px;
min-height: 60px; min-height: 60px;
padding: 12px; padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.20); border: 1px solid rgba(255, 255, 255, 0.2);
span { span {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -1527,7 +1738,7 @@ $alert-color: #101010;
background-color: transparent; background-color: transparent;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background-color: #D9D9D9; background-color: #d9d9d9;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background-color: transparent; background-color: transparent;
@ -1586,10 +1797,10 @@ $alert-color: #101010;
height: 16px; height: 16px;
&.pink { &.pink {
border: 2px solid #ce1c9c; border: 2px solid #ce1c9c;
background-color: #16417D; background-color: #16417d;
} }
&.white { &.white {
border: 2px solid #FFF; border: 2px solid #fff;
background-color: #001027; background-color: #001027;
} }
} }
@ -1614,7 +1825,7 @@ $alert-color: #101010;
.react-colorful__pointer { .react-colorful__pointer {
width: 15px; width: 15px;
height: 15px; height: 15px;
border: 4px solid #Fff; border: 4px solid #fff;
} }
.react-colorful__saturation { .react-colorful__saturation {
border-radius: 2px; border-radius: 2px;
@ -1704,7 +1915,6 @@ $alert-color: #101010;
min-height: 80px; min-height: 80px;
background-color: #fff; background-color: #fff;
} }
} }
// 치수선 설정 // 치수선 설정
@ -1824,10 +2034,22 @@ $alert-color: #101010;
border-radius: 50%; border-radius: 50%;
} }
} }
&:nth-child(1){ top: 0; left: 0; } &:nth-child(1) {
&:nth-child(2){ top: 0; right: 0; } top: 0;
&:nth-child(3){ bottom: 0; left: 0; } left: 0;
&:nth-child(4){ bottom: 0; right: 0; } }
&:nth-child(2) {
top: 0;
right: 0;
}
&:nth-child(3) {
bottom: 0;
left: 0;
}
&:nth-child(4) {
bottom: 0;
right: 0;
}
} }
.size-box { .size-box {
position: absolute; position: absolute;

View File

@ -521,9 +521,11 @@ export function isPointOnLine(line, point) {
const a = line.y2 - line.y1 const a = line.y2 - line.y1
const b = line.x1 - line.x2 const b = line.x1 - line.x2
const c = line.x2 * line.y1 - line.x1 * line.y2 const c = line.x2 * line.y1 - line.x1 * line.y2
return a * point.x + b * point.y + c === 0 const result = Math.abs(a * point.x + b * point.y + c) / 100
}
// 점이 선 위에 있는지 확인
return result <= 10
}
/** /**
* 점과 가까운 line 찾기 * 점과 가까운 line 찾기
* @param point * @param point

View File

@ -1,6 +1,14 @@
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util' import {
calculateIntersection,
distanceBetweenPoints,
findClosestPoint,
getDegreeByChon,
getDirectionByPoint,
isPointOnLine,
} from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
@ -958,10 +966,13 @@ export default function offsetPolygon(vertices, offset) {
} }
} }
export const splitPolygonWithLines = (polygon) => { /*export const splitPolygonWithLines = (polygon) => {
const roofs = [] const roofs = []
const allLines = [...polygon.innerLines] const allLines = [...polygon.innerLines]
const polygonLines = polygon.lines
const innerLines = polygon.innerLines
allLines.forEach((line) => { allLines.forEach((line) => {
line.startPoint = { x: line.x1, y: line.y1 } line.startPoint = { x: line.x1, y: line.y1 }
line.endPoint = { x: line.x2, y: line.y2 } line.endPoint = { x: line.x2, y: line.y2 }
@ -984,10 +995,10 @@ export const splitPolygonWithLines = (polygon) => {
}) })
}) })
/** /!**
* 좌표 테스트용 * 좌표 테스트용
*/ *!/
/*allLines.forEach((line) => { /!*allLines.forEach((line) => {
const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, { const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, {
left: line.startPoint.x, left: line.startPoint.x,
top: line.startPoint.y, top: line.startPoint.y,
@ -1016,10 +1027,10 @@ export const splitPolygonWithLines = (polygon) => {
polygon.canvas.add(text) polygon.canvas.add(text)
polygon.canvas.renderAll() polygon.canvas.renderAll()
})*/ })*!/
/** /!**
* 좌표 테스트용 * 좌표 테스트용
*/ *!/
polygon.points.forEach((point, index) => { polygon.points.forEach((point, index) => {
allLines.forEach((line) => { allLines.forEach((line) => {
@ -1165,6 +1176,273 @@ export const splitPolygonWithLines = (polygon) => {
polygon.canvas.add(roof) polygon.canvas.add(roof)
polygon.canvas.renderAll() polygon.canvas.renderAll()
}) })
}*/
export const splitPolygonWithLines = (polygon) => {
const canvas = polygon.canvas
polygon.set({ visible: false })
let innerLines = [...polygon.innerLines]
let polygonLines = [...polygon.lines]
const roofs = []
let delIndexs = []
let newLines = []
polygonLines.forEach((line, index) => {
line.tempIndex = index
innerLines.forEach((innerLine) => {
let newLine1, newLine2
if (isPointOnLine(line, innerLine.startPoint)) {
// 해당 line을 startPoint로 나눈 line2개를 canvas에 추가 하고 기존 line을 제거한다.
newLine1 = new QLine([line.x1, line.y1, innerLine.startPoint.x, innerLine.startPoint.y], {
fontSize: polygon.fontSize,
stroke: 'black',
strokeWidth: 3,
})
newLine2 = new QLine([innerLine.startPoint.x, innerLine.startPoint.y, line.x2, line.y2], {
fontSize: polygon.fontSize,
stroke: 'black',
strokeWidth: 3,
})
delIndexs.push(polygonLines.indexOf(line))
canvas.remove(polygonLines[polygonLines.indexOf(line)])
if (newLine1.length / 10 > 10) {
newLines.push(newLine1)
}
if (newLine2.length / 10 > 10) {
newLines.push(newLine2)
}
}
if (isPointOnLine(line, innerLine.endPoint)) {
newLine1 = new QLine([line.x1, line.y1, innerLine.endPoint.x, innerLine.endPoint.y], {
fontSize: polygon.fontSize,
stroke: 'black',
strokeWidth: 3,
})
newLine2 = new QLine([innerLine.endPoint.x, innerLine.endPoint.y, line.x2, line.y2], {
fontSize: polygon.fontSize,
stroke: 'black',
strokeWidth: 3,
})
delIndexs.push(polygonLines.indexOf(line))
canvas.remove(polygonLines[polygonLines.indexOf(line)])
if (newLine1.length / 10 > 10) {
newLines.push(newLine1)
}
if (newLine2.length / 10 > 10) {
newLines.push(newLine2)
}
}
})
})
polygonLines = polygonLines.filter((line) => !delIndexs.includes(line.tempIndex))
polygonLines = [...polygonLines, ...newLines]
const allLines = [...polygonLines, ...innerLines]
/**
* 왼쪽 상단을 startPoint로 전부 변경
*/
allLines.forEach((line) => {
let startPoint // 시작점
let endPoint // 끝점
if (line.x1 < line.x2) {
startPoint = { x: line.x1, y: line.y1 }
endPoint = { x: line.x2, y: line.y2 }
} else if (line.x1 > line.x2) {
startPoint = { x: line.x2, y: line.y2 }
endPoint = { x: line.x1, y: line.y1 }
} else {
if (line.y1 < line.y2) {
startPoint = { x: line.x1, y: line.y1 }
endPoint = { x: line.x2, y: line.y2 }
} else {
startPoint = { x: line.x2, y: line.y2 }
endPoint = { x: line.x1, y: line.y1 }
}
}
line.startPoint = startPoint
line.endPoint = endPoint
})
polygonLines.forEach((line) => {
const startPoint = line.startPoint // 시작점
let arrivalPoint = line.endPoint // 도착점
let currentPoint = startPoint
const roofPoints = [startPoint]
const startLine = line
const visitPoints = [startPoint]
const visitLines = [startLine]
let cnt = 0
while (!isSamePoint(currentPoint, arrivalPoint)) {
line.set({ stroke: 'red' })
canvas.renderAll()
let nextLines = allLines.filter(
(line2) =>
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
line !== line2 &&
innerLines.includes(line2) &&
!visitLines.includes(line2),
)
if (nextLines.length === 0) {
nextLines = allLines.filter(
(line2) =>
(isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) &&
line !== line2 &&
!visitLines.includes(line2),
)
}
if (!nextLines) {
break
}
let comparisonPoints = []
nextLines.forEach((nextLine) => {
if (isSamePoint(nextLine.startPoint, currentPoint)) {
comparisonPoints.push(nextLine.endPoint)
} else {
comparisonPoints.push(nextLine.startPoint)
}
})
comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point)))
comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint))
const minDistancePoint = comparisonPoints.reduce((prev, current) => {
const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2))
const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2))
return prevDistance < currentDistance ? prev : current
}, comparisonPoints[0])
nextLines.forEach((nextLine) => {
if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) {
visitLines.push(nextLine)
}
})
currentPoint = { ...minDistancePoint }
roofPoints.push(currentPoint)
cnt++
if (cnt > 100) {
throw new Error('무한루프')
}
}
roofs.push(roofPoints)
})
const newRoofs = removeDuplicatePolygons(roofs)
newRoofs.forEach((roofPoint, index) => {
let defense, pitch
const polygonLines = [...polygon.lines]
let representLines = []
let representLine
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
polygonLines.forEach((line) => {
let startFlag = false
let endFlag = false
const startPoint = line.startPoint
const endPoint = line.endPoint
roofPoint.forEach((point, index) => {
if (isSamePoint(point, startPoint)) {
startFlag = true
}
if (isSamePoint(point, endPoint)) {
endFlag = true
}
})
if (startFlag && endFlag) {
representLines.push(line)
}
})
// representLines중 가장 긴 line을 찾는다.
representLines.forEach((line) => {
if (!representLine) {
representLine = line
} else {
if (representLine.length < line.length) {
representLine = line
}
}
})
const direction = representLine.direction
switch (direction) {
case 'top':
defense = 'east'
break
case 'right':
defense = 'south'
break
case 'bottom':
defense = 'west'
break
case 'left':
defense = 'north'
break
}
pitch = polygon.lines[index].attributes?.pitch ?? 0
const roof = new QPolygon(roofPoint, {
fontSize: polygon.fontSize,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
name: POLYGON_TYPE.ROOF,
originX: 'center',
originY: 'center',
selectable: true,
defense: defense,
direction: defense,
pitch: pitch,
})
polygon.canvas.add(roof)
canvas.remove(polygon)
polygon.canvas.renderAll()
})
}
function normalizePoint(point) {
return {
x: Math.round(point.x),
y: Math.round(point.y),
}
}
function arePolygonsEqual(polygon1, polygon2) {
if (polygon1.length !== polygon2.length) return false
const normalizedPolygon1 = polygon1.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y)
const normalizedPolygon2 = polygon2.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y)
return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index]))
}
function removeDuplicatePolygons(polygons) {
const uniquePolygons = []
polygons.forEach((polygon) => {
const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon))
if (!isDuplicate) {
uniquePolygons.push(polygon)
}
})
return uniquePolygons
} }
const isSamePoint = (a, b) => { const isSamePoint = (a, b) => {
@ -3348,7 +3626,7 @@ function createRoofPaddingPolygon(polygon, lines, arcSegments = 0) {
} }
function arePointsEqual(point1, point2) { function arePointsEqual(point1, point2) {
return point1.x === point2.x && point1.y === point2.y return Math.abs(point1.x - point2.x) <= 1 && Math.abs(point1.y - point2.y) <= 1
} }
function arraysHaveSamePoints(array1, array2) { function arraysHaveSamePoints(array1, array2) {