Merge branch 'dev' into dev-yj

This commit is contained in:
yjnoh 2024-11-20 18:14:25 +09:00
commit da90a6d26a
24 changed files with 5407 additions and 4817 deletions

View File

@ -1,9 +1,53 @@
'ues client'
import { ErrorBoundary } from 'next/dist/client/components/error-boundary'
import ServerError from '../error'
// import { ErrorBoundary } from 'next/dist/client/components/error-boundary'
// import ServerError from '../error'
import { createContext, useEffect, useReducer, useState } from 'react'
export const FloorPlanProvider = ({ children }) => {
console.log('FloorPlanProvider')
return <ErrorBoundary fallback={<ServerError />}>{children}</ErrorBoundary>
const reducer = (prevState, nextState) => {
return { ...prevState, ...nextState }
}
const defaultEstimateData = {
estimateDate: new Date(), //견적일
charger: '', //담당자
objectName: '', //안건명
objectNameOmit: '', //경칭코드
estimateType: '', //주문분류
remarks: '', //비고
estimateOption: '', //견적특이사항
itemList: [],
fileList: [],
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
priceCd: '',
}
export const FloorPlanContext = createContext({
floorPlanState: {},
setFloorPlanState: () => {},
estimateContextState: {},
setEstimateContextState: () => {},
})
const FloorPlanProvider = ({ children }) => {
const [floorPlanState, setFloorPlanState] = useState({
// 플랜 파일 업로드 모달 오픈 제어
refFileModalOpen: false,
// 플랜 회전 모드 제어
toggleRotate: false,
})
const [estimateContextState, setEstimateContextState] = useReducer(reducer, defaultEstimateData)
useEffect(() => {
console.log('🚀 ~ floorPlanState:', floorPlanState)
}, [floorPlanState])
return (
<FloorPlanContext.Provider value={{ floorPlanState, setFloorPlanState, estimateContextState, setEstimateContextState }}>
{children}
</FloorPlanContext.Provider>
)
}
export default FloorPlanProvider

View File

@ -1,6 +1,6 @@
'use client'
import FloorPlan from '@/components/floor-plan/FloorPlan'
import { FloorPlanProvider } from './FloorPlanProvider'
import FloorPlanProvider from './FloorPlanProvider'
import CanvasLayout from '@/components/floor-plan/CanvasLayout'
export default function FloorPlanLayout({ children }) {

View File

@ -80,79 +80,32 @@ export default function Login() {
e.preventDefault()
const formData = new FormData(e.target)
///////////////////////////////////////////////////////////
//
setSession({
userId: 'NEW0166102',
saleStoreId: null,
name: null,
mail: null,
tel: null,
storeId: 'TEMP02',
userNm: 'ㅇㅇ6610',
userNmKana: '신규사용자 16610',
category: '인상6610',
telNo: '336610',
fax: null,
email: 't10t@naver.com',
pwdInitYn: 'Y',
storeLvl: '1',
groupId: '60000',
custCd: '100000',
})
setSessionState({
userId: 'NEW0166102',
saleStoreId: null,
name: null,
mail: null,
tel: null,
storeId: 'TEMP02',
userNm: 'ㅇㅇ6610',
userNmKana: '신규사용자 16610',
category: '인상6610',
telNo: '336610',
fax: null,
email: 't10t@naver.com',
pwdInitYn: 'Y',
storeLvl: '1',
groupId: '60000',
custCd: '100000',
})
if (chkLoginId) {
Cookies.set('chkLoginId', formData.get('id'), { expires: 7 })
} else {
Cookies.remove('chkLoginId')
//
const param = {
loginId: formData.get('id'),
pwd: formData.get('password'),
}
router.push('/')
//
///////////////////////////////////////////////////////////
// - ** **
// const param = {
// loginId: formData.get('id'),
// pwd: formData.get('password'),
// }
// await promisePost({ url: '/api/login/v1.0/login', data: param })
// .then((res) => {
// if (res) {
// if (res.data.result.resultCode === 'S') {
// setSession(res.data.data)
// setSessionState(res.data.data)
// // ID SAVE ,
// if (chkLoginId) {
// Cookies.set('chkLoginId', formData.get('id'), { expires: 7 })
// } else {
// Cookies.remove('chkLoginId')
// }
// router.push('/')
// } else {
// alert(res.data.result.resultMsg)
// }
// }
// })
// .catch((error) => {
// alert(error.response.data.message)
// })
await promisePost({ url: '/api/login/v1.0/login', data: param })
.then((res) => {
if (res) {
if (res.data.result.resultCode === 'S') {
setSession(res.data.data)
setSessionState(res.data.data)
// ID SAVE ,
if (chkLoginId) {
Cookies.set('chkLoginId', formData.get('id'), { expires: 7 })
} else {
Cookies.remove('chkLoginId')
}
router.push('/')
} else {
alert(res.data.result.resultMsg)
}
}
})
.catch((error) => {
alert(error.response.data.message)
})
}
//

View File

@ -58,7 +58,7 @@ export default function Estimate({ params }) {
const objectRecoil = useRecoilValue(floorPlanObjectState)
//
const { state, setState, addItem, handleEstimateFileDownload } = useEstimateController(params.pid)
const { estimateContextState, setEstimateContextState, addItem, handleEstimateFileDownload } = useEstimateController(params.pid)
// List
const [specialNoteList, setSpecialNoteList] = useState([])
@ -105,9 +105,9 @@ export default function Estimate({ params }) {
let url = `/api/estimate/special-note-list`
get({ url: url }).then((res) => {
if (isNotEmptyArray(res)) {
if (state?.estimateOption) {
if (estimateContextState?.estimateOption) {
res.map((row) => {
let estimateOption = state?.estimateOption?.split('、')
let estimateOption = estimateContextState?.estimateOption?.split('、')
row.text = false
estimateOption.map((row2) => {
if (row2 === row.code) {
@ -119,16 +119,21 @@ export default function Estimate({ params }) {
}
}
})
}, [state?.estimateOption])
}, [estimateContextState?.estimateOption])
// set
useEffect(() => {
let estimateDate = dayjs(startDate).format('YYYY-MM-DD')
setState({ estimateDate: estimateDate })
setEstimateContextState({ estimateDate: estimateDate })
}, [startDate])
//API
useEffect(() => {
// setState
setStartDate(estimateContextState?.estimateDate)
}, [estimateContextState?.estimateDate])
useEffect(() => {
// setEstimateContextState
if (isNotEmptyArray(specialNoteList)) {
const liveCheckedData = specialNoteList.filter((row) => row.text === true)
@ -138,7 +143,7 @@ export default function Estimate({ params }) {
}
const newData = data.join('、')
setState({ estimateOption: newData })
setEstimateContextState({ estimateOption: newData })
}
}, [specialNoteList])
@ -148,23 +153,23 @@ export default function Estimate({ params }) {
event.stopPropagation()
}
// state
// estimateContextState
useEffect(() => {
if (isNotEmptyArray(files)) {
files.map((row) => {
setState({ fileList: row.data })
setEstimateContextState({ fileList: row.data })
})
} else {
setState({ fileList: [] })
setEstimateContextState({ fileList: [] })
}
}, [files])
// set
useEffect(() => {
if (isNotEmptyArray(state.fileList)) {
setOriginFiles(state.fileList)
if (isNotEmptyArray(estimateContextState.fileList)) {
setOriginFiles(estimateContextState.fileList)
}
}, [state?.fileList])
}, [estimateContextState?.fileList])
//
const deleteOriginFile = async (objectNo, no) => {
@ -176,7 +181,7 @@ export default function Estimate({ params }) {
await promisePost({ url: 'api/file/fileDelete', data: delParams }).then((res) => {
if (res.status === 204) {
setOriginFiles(originFiles.filter((file) => file.objectNo === objectNo && file.no !== no))
setState({
setEstimateContextState({
fileList: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no),
})
}
@ -185,11 +190,11 @@ export default function Estimate({ params }) {
// option &&
useEffect(() => {
if (state.estimateType !== '') {
if (estimateContextState.estimateType !== '') {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
docTpCd: state?.estimateType,
docTpCd: estimateContextState?.estimateType,
}
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
@ -198,14 +203,16 @@ export default function Estimate({ params }) {
setStorePriceList(res.data)
}
})
setItemChangeYn(true)
}
}, [state?.estimateType])
}, [estimateContextState?.estimateType])
useEffect(() => {
if (state?.priceCd) {
setShowPriceCd(state.priceCd)
if (estimateContextState?.priceCd) {
setShowPriceCd(estimateContextState.priceCd)
}
}, [state?.priceCd])
}, [estimateContextState?.priceCd])
// option
const onChangeStorePriceList = (priceCd) => {
@ -215,13 +222,9 @@ export default function Estimate({ params }) {
docTpCd: priceCd,
}
// tempPriceCd ?
// priceCd setState
// priceCd setEstimateContextState
// showPriceCd
setShowPriceCd(priceCd)
//setState({
// tempPriceCd: priceCd,
//})
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
get({ url: apiUrl }).then((res) => {
@ -232,14 +235,14 @@ export default function Estimate({ params }) {
}
//Pricing
const handlePricing = async (priceCd) => {
const handlePricing = async (showPriceCd) => {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
docTpCd: state.estimateType,
priceCd: priceCd,
//itemIdList: state.itemList, // delFlg 0..
itemIdList: state.itemList.filter((item) => item.delFlg === '0'),
docTpCd: estimateContextState.estimateType,
priceCd: showPriceCd,
//itemIdList: estimateContextState.itemList, // delFlg 0..
itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0'),
}
if (param.itemIdList.length > 0) {
@ -266,7 +269,7 @@ export default function Estimate({ params }) {
//itemId
if (data.result.code === 200) {
if (isNotEmptyArray(data.data2)) {
state.itemList.map((item) => {
estimateContextState.itemList.map((item) => {
let checkYn = false
data.data2.map((item2) => {
if (item2.itemId === item.itemId) {
@ -280,8 +283,8 @@ export default function Estimate({ params }) {
}
})
setState({
priceCd: priceCd,
setEstimateContextState({
priceCd: showPriceCd,
itemList: updateList,
})
@ -305,11 +308,31 @@ export default function Estimate({ params }) {
setSelection(newSelection)
}
//PKG input
const onChangePkgAsp = (value) => {
if (estimateContextState.estimateType === 'YJSS') {
let pkgAsp = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
if (isNaN(pkgAsp)) {
pkgAsp = 0
} else {
pkgAsp = pkgAsp.toLocaleString()
}
// PKG
let totVolKw = estimateContextState.totVolKw * 1000
let pkgTotPrice = pkgAsp * totVolKw
setEstimateContextState({
pkgAsp: pkgAsp,
pkgTotPrice: pkgTotPrice.toFixed(3),
})
}
}
//
const onChangeAmount = (value, dispOrder, index) => {
//itemChangeFlg = 1, partAdd = 0
let amount = value
amount = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
let amount = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
if (isNaN(amount)) {
amount = 0
} else {
@ -321,9 +344,9 @@ export default function Estimate({ params }) {
updates.amount = amount
updates.itemChangeFlg = '1'
updates.partAdd = '0'
updates.saleTotPrice = (Number(amount.replaceAll(',', '')) * state.itemList[index].salePrice.replaceAll(',', '')).toLocaleString()
updates.saleTotPrice = (Number(amount.replaceAll(',', '')) * estimateContextState.itemList[index].salePrice.replaceAll(',', '')).toLocaleString()
updateList = state.itemList.map((item) => {
updateList = estimateContextState.itemList.map((item) => {
if (item.dispOrder === dispOrder) {
return { ...item, ...updates }
} else {
@ -331,7 +354,7 @@ export default function Estimate({ params }) {
}
})
setState({
setEstimateContextState({
itemList: updateList,
})
@ -341,8 +364,7 @@ export default function Estimate({ params }) {
//
const onChangeSalePrice = (value, dispOrder, index) => {
//itemChangeFlg, partAdd
let salePrice
salePrice = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
let salePrice = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
if (isNaN(salePrice)) {
salePrice = 0
} else {
@ -351,9 +373,9 @@ export default function Estimate({ params }) {
let updateList = []
let updates = {}
updates.salePrice = salePrice
updates.saleTotPrice = (Number(salePrice.replaceAll(',', '')) * state.itemList[index].amount.replaceAll(',', '')).toLocaleString()
updates.saleTotPrice = (Number(salePrice.replaceAll(',', '')) * estimateContextState.itemList[index].amount.replaceAll(',', '')).toLocaleString()
updateList = state.itemList.map((item) => {
updateList = estimateContextState.itemList.map((item) => {
if (item.dispOrder === dispOrder) {
return { ...item, ...updates }
} else {
@ -361,7 +383,7 @@ export default function Estimate({ params }) {
}
})
setState({
setEstimateContextState({
itemList: updateList,
})
@ -377,7 +399,7 @@ export default function Estimate({ params }) {
let updateList = []
let updates = {}
get({ url: apiUrl }).then((res) => {
console.log('아이템상세정보:::::::', res)
// console.log(':::::::', res)
updates.objectNo = objectNo
updates.planNo = planNo
updates.itemId = res.itemId
@ -397,11 +419,12 @@ export default function Estimate({ params }) {
updates.specialNoteCd = res.spnAttrCds
updates.itemGroup = res.itemGroup
updates.delFlg = '0' // 0
updates.saleTotPrice = res.salePrice * state.itemList[index].amount
// updates.saleTotPrice = res.salePrice * estimateContextState.itemList[index].amount
updates.saleTotPrice = '0' // 0
//104671
let bomList = res.itemBomList
updateList = state.itemList.map((item) => {
updateList = estimateContextState.itemList.map((item) => {
if (item.dispOrder === dispOrder) {
return { ...item, ...updates }
} else if (item.paDispOrder === dispOrder) {
@ -413,18 +436,18 @@ export default function Estimate({ params }) {
//paDispOrder
if (bomList) {
bomList.map((bomItem, index) => {
let newItemDispOrder = Math.max(...state.itemList.map((item) => item.dispOrder))
let newItemDispOrder = Math.max(...estimateContextState.itemList.map((item) => item.dispOrder))
bomItem.dispOrder = index + 1 + newItemDispOrder
bomItem.delFlg = '0'
bomItem.objectNo = objectNo
bomItem.planNo = planNo
})
setState({
setEstimateContextState({
itemList: [...updateList, ...bomList],
})
} else {
setState({
setEstimateContextState({
itemList: updateList,
})
}
@ -437,7 +460,7 @@ export default function Estimate({ params }) {
const removeItem = () => {
const array = [...selection]
let delList = []
state.itemList.filter((row) => {
estimateContextState.itemList.filter((row) => {
array.map((row2) => {
if (row2 === row.dispOrder) {
delList.push({ ...row })
@ -448,7 +471,7 @@ export default function Estimate({ params }) {
})
})
const updateList = state.itemList.map((item) => {
const updateList = estimateContextState.itemList.map((item) => {
const isDeleted = delList.some((row) => item.delFlg === '1' || item.dispOrder === row.dispOrder)
return {
...item,
@ -467,7 +490,7 @@ export default function Estimate({ params }) {
return alert(getMessage('estimate.detail.save.requiredItem'))
}
setState({
setEstimateContextState({
itemList: updateList,
})
@ -477,31 +500,94 @@ export default function Estimate({ params }) {
useEffect(() => {
if (itemChangeYn) {
// console.log(' ::::::::::', state.itemList)
// console.log(' ::::::', itemList)
//delFlg 0 ..
//(PCS) : totAmount
//( Kw) : totVolKw
// : supplyPrice
//(10%) : vatPrice
// :totPrice
let totAmount = 0
let amount = 0
state.itemList.map((item) => {
if (item.delFlg === '0') {
amount = item.amount.replace(/[^0-9]/g, '').replaceAll(',', '')
totAmount += Number(amount)
}
})
let totVolKw = 0
let supplyPrice = 0
let vatPrice = 0
let totPrice = 0
let addPkgPrice = 0
if (estimateContextState.estimateType === 'YJOD') {
estimateContextState.itemList.map((item) => {
if (item.delFlg === '0') {
const amount = Number(item.amount.replace(/[^0-9]/g, '').replaceAll(',', ''))
const price = Number(item.saleTotPrice.replaceAll(',', ''))
setState({
totAmount: totAmount,
})
if (item.moduleFlg === '1') {
//(Kw) 1
const volKw = (item.pnowW * amount) / 1000
totVolKw += volKw
}
// const price
totAmount += amount
supplyPrice += price
}
})
vatPrice = supplyPrice * 0.1
totPrice = supplyPrice + vatPrice
setEstimateContextState({
totAmount: totAmount,
totVolKw: totVolKw.toFixed(3),
supplyPrice: supplyPrice.toFixed(3),
vatPrice: vatPrice.toFixed(3),
totPrice: totPrice.toFixed(3),
})
} else {
//YJSS
estimateContextState.itemList.map((item) => {
if (item.delFlg === '0') {
const amount = Number(item.amount.replace(/[^0-9]/g, '').replaceAll(',', ''))
const price = Number(item.saleTotPrice.replaceAll(',', ''))
const salePrice = Number(item.salePrice.replaceAll(',', ''))
if (item.moduleFlg === '1') {
//(Kw) 1
const volKw = (item.pnowW * amount) / 1000
totVolKw += volKw
}
if (item.pkgMaterialFlg === '1') {
const pkgPrice = amount * salePrice
//YJSS PKG *
addPkgPrice += pkgPrice
}
// const price
totAmount += amount
supplyPrice += price
}
})
vatPrice = supplyPrice * 0.1
totPrice = supplyPrice + vatPrice
setEstimateContextState({
totAmount: totAmount,
totVolKw: totVolKw.toFixed(3),
supplyPrice: supplyPrice.toFixed(3),
vatPrice: vatPrice.toFixed(3),
totPrice: totPrice.toFixed(3),
})
}
setItemChangeYn(false)
}
}, [itemChangeYn])
}, [itemChangeYn, estimateContextState.itemList])
//
const handleBlurObjectName = (e) => {
setEstimateContextState({ objectName: e.target.value })
}
//
const handleBlurCharger = (e) => {
setEstimateContextState({ charger: e.target.value })
}
//
const handleBlurRemarks = (e) => {
setEstimateContextState({ remarks: e.target.value })
}
return (
<div className="sub-content estimate">
@ -518,17 +604,21 @@ export default function Estimate({ params }) {
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.docNo')}</div>
<div className="estimate-name">{state.docNo}</div>
<div className="estimate-name">{estimateContextState.docNo}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.drawingEstimateCreateDate')}</div>
<div className="estimate-name">
{state?.drawingEstimateCreateDate ? `${dayjs(state.drawingEstimateCreateDate).format('YYYY.MM.DD')}` : ''}
{estimateContextState?.drawingEstimateCreateDate
? `${dayjs(estimateContextState.drawingEstimateCreateDate).format('YYYY.MM.DD')}`
: ''}
</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.lastEditDatetime')}</div>
<div className="estimate-name">{state?.lastEditDatetime ? `${dayjs(state.lastEditDatetime).format('YYYY.MM.DD HH:mm')}` : ''}</div>
<div className="estimate-name">
{estimateContextState?.lastEditDatetime ? `${dayjs(estimateContextState.lastEditDatetime).format('YYYY.MM.DD HH:mm')}` : ''}
</div>
</div>
</div>
</div>
@ -554,7 +644,7 @@ export default function Estimate({ params }) {
<tr>
{/* 1차 판매점명 */}
<th>{getMessage('estimate.detail.saleStoreId')}</th>
<td>{state?.firstSaleStoreName}</td>
<td>{estimateContextState?.firstSaleStoreName}</td>
{/* 견적일 */}
<th>
{getMessage('estimate.detail.estimateDate')} <span className="important">*</span>
@ -568,22 +658,14 @@ export default function Estimate({ params }) {
<tr>
{/* 2차 판매점명 */}
<th>{getMessage('estimate.detail.otherSaleStoreId')}</th>
<td>{state?.agencySaleStoreName}</td>
<td>{estimateContextState?.agencySaleStoreName}</td>
{/* 담당자 */}
<th>
{getMessage('estimate.detail.receiveUser')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '350px' }}>
<input
type="text"
className="input-light"
defaultValue={state?.charger}
onChange={(e) => {
// charger
setState({ charger: e.target.value })
}}
/>
<input type="text" className="input-light" defaultValue={estimateContextState?.charger} onBlur={handleBlurCharger} />
</div>
</td>
</tr>
@ -595,15 +677,7 @@ export default function Estimate({ params }) {
<td colSpan={3}>
<div className="form-flex-wrap">
<div className="input-wrap mr5" style={{ width: '610px' }}>
<input
type="text"
className="input-light"
defaultValue={state?.objectName}
onChange={(e) => {
// objectName
setState({ objectName: e.target.value })
}}
/>
<input type="text" className="input-light" defaultValue={estimateContextState?.objectName} onBlur={handleBlurObjectName} />
</div>
<div className="select-wrap" style={{ width: '200px' }}>
<Select
@ -615,17 +689,17 @@ export default function Estimate({ params }) {
options={honorificCodeList}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
setState({ objectNameOmit: e.clCodeNm })
setEstimateContextState({ objectNameOmit: e.clCodeNm })
} else {
setState({ objectNameOmit: '' })
setEstimateContextState({ objectNameOmit: '' })
}
}}
getOptionLabel={(x) => x.clCodeNm}
getOptionValue={(x) => x.clCode}
isClearable={true}
isClearable={false}
isSearchable={false}
value={honorificCodeList.filter(function (option) {
return option.clCodeNm === state.objectNameOmit
return option.clCodeNm === estimateContextState.objectNameOmit
})}
/>
</div>
@ -635,7 +709,7 @@ export default function Estimate({ params }) {
<tr>
{/* 물건정보에서 입력한 메모 */}
<th>{getMessage('estimate.detail.objectRemarks')}</th>
<td colSpan={3}>{state?.objectRemarks}</td>
<td colSpan={3}>{estimateContextState?.objectRemarks}</td>
</tr>
<tr>
{/* 주문분류 */}
@ -650,13 +724,13 @@ export default function Estimate({ params }) {
name="estimateType"
id="YJSS"
value={'YJSS'}
checked={state?.estimateType === 'YJSS' ? true : false}
checked={estimateContextState?.estimateType === 'YJSS' ? true : false}
onChange={(e) => {
//
setState({ estimateType: e.target.value })
setEstimateContextState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJSS">住宅PKG</label>
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
</div>
<div className="d-check-radio light">
<input
@ -664,12 +738,12 @@ export default function Estimate({ params }) {
name="estimateType"
id="YJOD"
value={'YJOD'}
checked={state?.estimateType === 'YJOD' ? true : false}
checked={estimateContextState?.estimateType === 'YJOD' ? true : false}
onChange={(e) => {
setState({ estimateType: e.target.value })
setEstimateContextState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJOD">積上げ( YJOD )</label>
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
</div>
</div>
</td>
@ -678,20 +752,20 @@ export default function Estimate({ params }) {
{/* 지붕재・사양시공 최대4개*/}
<th>{getMessage('estimate.detail.roofCns')}</th>
<td colSpan={3}>
{state?.roofMaterialIdMulti?.split('、').map((row, index) => {
{estimateContextState?.roofMaterialIdMulti?.split('、').map((row, index) => {
//
let roofList = row
let roofListLength = state?.roofMaterialIdMulti?.split('、').length
let roofListLength = estimateContextState?.roofMaterialIdMulti?.split('、').length
let style = 'mb5'
if (roofListLength == index + 1) {
style = ''
}
//
let constructSpecificationMulti = state?.constructSpecificationMulti?.split('、')
let constructSpecificationMulti = estimateContextState?.constructSpecificationMulti?.split('、')
return (
<>
<div className={`form-flex-wrap ${style}`} key={uuidv4()}>
<div className={`form-flex-wrap ${style}`} key={fixedKey}>
<div className="input-wrap mr5" style={{ width: '610px' }} key={`roof${index}`}>
<input type="text" className="input-light" value={roofList} readOnly />
</div>
@ -709,15 +783,7 @@ export default function Estimate({ params }) {
<th>{getMessage('estimate.detail.remarks')}</th>
<td colSpan={3}>
<div className="input-wrap">
<input
type="text"
className="input-light"
defaultValue={state?.remarks}
onChange={(e) => {
//
setState({ remarks: e.target.value })
}}
/>
<input type="text" className="input-light" defaultValue={estimateContextState?.remarks} onBlur={handleBlurRemarks} />
</div>
</td>
</tr>
@ -732,9 +798,9 @@ export default function Estimate({ params }) {
<input
type="checkbox"
id="next"
checked={state?.fileFlg === '0' ? false : true}
checked={estimateContextState?.fileFlg === '0' ? false : true}
onChange={(e) => {
setState({
setEstimateContextState({
fileFlg: e.target.checked ? '1' : '0',
})
}}
@ -876,28 +942,28 @@ export default function Estimate({ params }) {
<div className="estimate-list-wrap one">
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totAmount')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(state?.totAmount)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(estimateContextState?.totAmount)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totVolKw')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(state?.totVolKw)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(estimateContextState?.totVolKw)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.supplyPrice')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(state?.supplyPrice)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(estimateContextState?.supplyPrice)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.vatPrice')}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(state?.vatPrice)}</div>
<div className="estimate-name blue">{convertNumberToPriceDecimal(estimateContextState?.vatPrice)}</div>
</div>
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.totPrice')}</div>
<div className="estimate-name red">{convertNumberToPriceDecimal(state?.totPrice)}</div>
<div className="estimate-name red">{convertNumberToPriceDecimal(estimateContextState?.totPrice)}</div>
</div>
</div>
</div>
{/* YJOD면 아래영역 숨김 */}
<div className="common-table bt-able" style={{ display: state?.estimateType === 'YJSS' ? '' : 'none' }}>
<div className="common-table bt-able" style={{ display: estimateContextState?.estimateType === 'YJSS' ? '' : 'none' }}>
<table>
<colgroup>
<col style={{ width: '160px' }} />
@ -915,13 +981,20 @@ export default function Estimate({ params }) {
</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" />
<input
type="text"
className="input-light"
value={estimateContextState?.pkgAsp}
onChange={(e) => {
onChangePkgAsp(e.target.value)
}}
/>
</div>
</td>
<th>{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgWeight')}</th>
<td>{getMessage('estimate.detail.sepcialEstimateProductInfo.calcFormula1')}</td>
<td>{convertNumberToPriceDecimal(estimateContextState?.totVolKw)}</td>
<th>{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgPrice')}</th>
<td>{getMessage('estimate.detail.sepcialEstimateProductInfo.calcFormula2')}</td>
<td>{convertNumberToPriceDecimal(estimateContextState?.pkgTotPrice)}</td>
</tr>
</tbody>
</table>
@ -953,7 +1026,7 @@ export default function Estimate({ params }) {
type="button"
className="btn-origin grey ml5"
onClick={() => {
handlePricing(state?.priceCd)
handlePricing(showPriceCd)
}}
>
{getMessage('estimate.detail.showPrice.pricingBtn')}
@ -1030,121 +1103,113 @@ export default function Estimate({ params }) {
</tr>
</thead>
<tbody>
{state?.itemList.length > 0 &&
state.itemList.map((item, index) => {
{estimateContextState?.itemList.length > 0 &&
estimateContextState.itemList.map((item, index) => {
if (item.delFlg === '0') {
return (
<>
<tr key={fixedKey}>
<td className="al-c">
<div className="d-check-box light no-text">
<input
type="checkbox"
id={item?.dispOrder}
disabled={item?.paDispOrder ? true : false}
onChange={() => onChangeSelect(item.dispOrder)}
checked={selection.has(item.dispOrder) ? true : false}
<tr key={item?.dispOrder || index}>
<td className="al-c">
<div className="d-check-box light no-text">
<input
type="checkbox"
id={item?.dispOrder}
disabled={!!item?.paDispOrder}
onChange={() => onChangeSelect(item.dispOrder)}
checked={!!selection.has(item.dispOrder)}
/>
<label htmlFor={item?.dispOrder}></label>
</div>
</td>
<td className="al-r">{item?.dispOrder}</td>
<td>
<div className="form-flex-wrap">
<div className="select-wrap mr5">
<Select
id="long-value-select1"
instanceId="long-value-select1"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={displayItemList}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
onChangeDisplayItem(e.itemId, item.dispOrder, index)
}
}}
getOptionLabel={(x) => x.itemName}
getOptionValue={(x) => x.itemId}
isClearable={false}
isDisabled={!!item?.paDispOrder}
value={displayItemList.filter(function (option) {
return option.itemId === item.itemId
})}
/>
<label htmlFor={item?.dispOrder}></label>
</div>
</td>
<td className="al-r">{item?.dispOrder}</td>
<td>
<div className="form-flex-wrap">
<div className="select-wrap mr5">
<Select
id="long-value-select1"
instanceId="long-value-select1"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={displayItemList}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
onChangeDisplayItem(e.itemId, item.dispOrder, index)
}
}}
getOptionLabel={(x) => x.itemName}
getOptionValue={(x) => x.itemId}
isClearable={false}
isDisabled={item?.paDispOrder ? true : false}
value={displayItemList.filter(function (option) {
return option.itemId === item.itemId
})}
/>
{item?.itemChangeFlg === '1' && (
<div className="btn-area">
<span className="tb_ico change_check"></span>
</div>
{item?.itemChangeFlg === '1' && (
<div className="btn-area">
<span className="tb_ico change_check"></span>
</div>
)}
</div>
</td>
<td>
<div className="form-flex-wrap">
<div className="name">{item?.itemNo}</div>
<div className="icon-wrap">
{item?.fileUploadFlg === '1' && <span className="tb_ico file_check"></span>}
{item?.specialNoteCd && (
<button
type="button"
className="grid-tip"
onClick={() => {
setProductFeaturesPopupOpen(true)
setShowProductFeatureData(item?.specialNoteCd)
}}
></button>
)}
</div>
</td>
<td>
<div className="form-flex-wrap">
<div className="name">{item?.itemNo}</div>
<div className="icon-wrap">
{item?.fileUploadFlg === '1' && <span className="tb_ico file_check"></span>}
{item?.specialNoteCd && (
<button
type="button"
className="grid-tip"
onClick={() => {
setProductFeaturesPopupOpen(true)
setShowProductFeatureData(item?.specialNoteCd)
}}
></button>
)}
</div>
</div>
</td>
<td>
<div className="input-wrap" style={{ width: '100%' }}>
</div>
</td>
<td>
<div className="input-wrap" style={{ width: '100%' }}>
<input
type="text"
className="input-light al-r"
value={item?.amount}
disabled={item.itemId === '' || !!item?.paDispOrder}
onChange={(e) => {
onChangeAmount(e.target.value, item.dispOrder, index)
}}
/>
</div>
</td>
<td>{item.unit}</td>
<td>
<div className="form-flex-wrap">
<div className="input-wrap mr5">
<input
type="text"
className="input-light al-r"
value={item?.amount}
disabled={item.itemId === '' ? true : item?.paDispOrder ? true : false}
value={convertNumberToPriceDecimal(item?.salePrice.replaceAll(',', ''))}
disabled={
estimateContextState?.estimateType === 'YJSS'
? item?.paDispOrder
? true
: item.pkgMaterialFlg !== '1'
: item.itemId === '' || !!item?.paDispOrder
}
onChange={(e) => {
onChangeAmount(e.target.value, item.dispOrder, index)
onChangeSalePrice(e.target.value, item.dispOrder, index)
}}
/>
</div>
</td>
<td>{item.unit}</td>
<td>
<div className="form-flex-wrap">
<div className="input-wrap mr5">
<input
type="text"
className="input-light al-r"
value={item?.salePrice}
disabled={
state?.estimateType === 'YJSS'
? item?.paDispOrder
? true
: item.pkgMaterialFlg === '1'
? false
: true
: item.itemId === ''
? true
: item?.paDispOrder
? true
: false
}
onChange={(e) => {
onChangeSalePrice(e.target.value, item.dispOrder, index)
}}
/>
</div>
{/* <div className="btn-area">
{/* <div className="btn-area">
<span className="tb_ico open_check">OPEN아이콘 처리</span>
</div> */}
</div>
</td>
<td className="al-r">{item?.saleTotPrice}</td>
</tr>
</>
</div>
</td>
<td className="al-r">{convertNumberToPriceDecimal(item?.saleTotPrice.replaceAll(',', ''))}</td>
</tr>
)
} else {
return null

View File

@ -0,0 +1,313 @@
'use client'
import { useEffect, useState, useContext, useRef } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useAxios } from '@/hooks/useAxios'
import Select, { components } from 'react-select'
import { SessionContext } from '@/app/SessionProvider'
import { isEmptyArray, isObjectNotEmpty } from '@/util/common-utils'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
export default function EstimateCopyPop({ planNo, setEstimateCopyPopupOpen }) {
const { getMessage } = useMessage()
const { get } = useAxios()
const { handleEstimateCopy, state } = useEstimateController(planNo)
const { session } = useContext(SessionContext)
const [saleStoreList, setSaleStoreList] = useState([]) //
const [favoriteStoreList, setFavoriteStoreList] = useState([]) //
const [showSaleStoreList, setShowSaleStoreList] = useState([]) //
const [otherSaleStoreList, setOtherSaleStoreList] = useState([])
const [originOtherSaleStoreList, setOriginOtherSaleStoreList] = useState([])
const [saleStoreId, setSaleStoreId] = useState('') // 1
const [otherSaleStoreId, setOtherSaleStoreId] = useState('') // 1
const [sendPlanNo, setSendPlanNo] = useState('1')
const [copyReceiveUser, setCopyReceiveUser] = useState('')
const ref = useRef() //2
useEffect(() => {
let url
let firstList
let favList
let otherList
if (session.storeId === 'T01') {
url = `/api/object/saleStore/${session?.storeId}/firstList?userId=${session?.userId}`
} else {
if (session.storeLvl === '1') {
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}`
} else {
//T01 or 1
// url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}`
}
}
get({ url: url }).then((res) => {
if (!isEmptyArray(res)) {
res.map((row) => {
//
row.value = row.saleStoreId
row.label = row.saleStoreName
})
if (session.storeId === 'T01') {
firstList = res
//T01
firstList.sort((a, b) => (a.saleStoreId !== 'T01') - (b.saleStoreId !== 'T01') || a.saleStoreId - b.saleStoreId)
favList = firstList.filter((row) => row.saleStoreId === 'T01' || row.priority !== 'B')
setSaleStoreList(firstList)
setFavoriteStoreList(favList)
setShowSaleStoreList(favList)
setSaleStoreId(session?.storeId)
// T01 T01 2 (onSelectionChange..) 2
url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}`
get({ url: url }).then((res) => {
if (!isEmptyArray(res)) {
res.map((row) => {
row.value == row.saleStoreId
row.label = row.saleStoreName
})
otherList = res
setOtherSaleStoreList(otherList)
setOriginOtherSaleStoreList(otherList)
} else {
setOtherSaleStoreList([])
}
})
} else {
if (session.storeLvl === '1') {
firstList = res
favList = res.filter((row) => row.priority !== 'B')
otherList = res.filter((row) => row.firstAgentYn === 'N')
setSaleStoreList(firstList)
setFavoriteStoreList(firstList)
setShowSaleStoreList(firstList)
setSaleStoreId(firstList[0].saleStoreId)
setOtherSaleStoreList(otherList)
} else {
//T01 or 1
}
}
}
})
}, [])
useEffect(() => {
if (planNo) {
setSendPlanNo(planNo)
}
}, [planNo])
useEffect(() => {
if (state?.charger) {
setCopyReceiveUser(state.charger)
}
}, [state.charger])
//T01 1
const onInputChange = (key) => {
if (key !== '') {
setShowSaleStoreList(saleStoreList)
} else {
setShowSaleStoreList(favoriteStoreList)
}
}
// 1
const onSelectionChange = (key) => {
if (isObjectNotEmpty(key)) {
if (key.saleStoreId === saleStoreId) {
return
}
}
if (isObjectNotEmpty(key)) {
setSaleStoreId(key.saleStoreId)
const url = `/api/object/saleStore/${key.saleStoreId}/list?firstFlg=0&userId=${session?.userId}`
let otherList
get({ url: url }).then((res) => {
if (!isEmptyArray(res)) {
res.map((row) => {
row.value = row.saleStoreId
row.label = row.saleStoreName
})
otherList = res
setOtherSaleStoreList(otherList)
setOtherSaleStoreId('')
} else {
setOtherSaleStoreId('')
setOtherSaleStoreList([])
}
})
} else {
setSaleStoreId('')
//otherSaleStoreId onSelectionChange2
setOtherSaleStoreList(originOtherSaleStoreList)
handleClear()
}
}
// 2
const onSelectionChange2 = (key) => {
if (isObjectNotEmpty(key)) {
if (key.saleStoreId === otherSaleStoreId) {
return
}
}
if (isObjectNotEmpty(key)) {
setOtherSaleStoreId(key.saleStoreId)
} else {
setOtherSaleStoreId('')
}
}
//2
const handleClear = () => {
if (ref.current) {
ref.current.clearValue()
}
}
return (
<div className="modal-popup">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('estimate.detail.estimateCopyPopup.title')}</h1>
<button
type="button"
className="modal-close"
onClick={() => {
setEstimateCopyPopupOpen(false)
}}
>
{getMessage('estimate.detail.estimateCopyPopup.close')}
</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="explane">{getMessage('estimate.detail.estimateCopyPopup.explane')}</div>
<div className="estimate-copy-info-wrap">
<div className="estimate-copy-info-item">
<div className="estimate-copy-info-tit">
{getMessage('estimate.detail.estimateCopyPopup.label.saleStoreId')} <span className="red">*</span>
</div>
{session.storeId === 'T01' && (
<div className="estimate-copy-info-box">
<div className="estimate-copy-sel">
<Select
id="long-value-select1"
instanceId="long-value-select1"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={showSaleStoreList}
onInputChange={onInputChange}
onChange={onSelectionChange}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isClearable={true}
isDisabled={false}
value={saleStoreList.filter(function (option) {
return option.saleStoreId === saleStoreId
})}
/>
</div>
<div className="estimate-copy-id">{saleStoreId}</div>
</div>
)}
{session.storeId !== 'T01' && session.storeLvl === '1' && (
<div className="estimate-copy-info-box">
<div className="estimate-copy-sel">
<Select
id="long-value-select1"
instanceId="long-value-select1"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={showSaleStoreList[0]}
onChange={onSelectionChange}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isClearable={false}
isDisabled={session?.storeLvl !== '1' ? true : session?.storeId !== 'T01' ? true : false}
value={showSaleStoreList.filter(function (option) {
return option.saleStoreId === saleStoreId
})}
/>
</div>
<div className="estimate-copy-id">{saleStoreId}</div>
</div>
)}
</div>
<div className="estimate-copy-info-item">
<div className="estimate-copy-info-tit">{getMessage('estimate.detail.estimateCopyPopup.label.otherSaleStoreId')}</div>
<div className="estimate-copy-info-box">
<div className="estimate-copy-sel">
<Select
id="long-value-select2"
instanceId="long-value-select2"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
ref={ref}
options={otherSaleStoreList}
onChange={onSelectionChange2}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isClearable={true}
isDisabled={otherSaleStoreList.length > 0 ? false : true}
value={otherSaleStoreList.filter(function (option) {
return option.saleStoreId === otherSaleStoreId
})}
/>
</div>
<div className="estimate-copy-id">{otherSaleStoreId}</div>
</div>
</div>
<div className="estimate-copy-info-item">
<div className="estimate-copy-info-tit">
{getMessage('estimate.detail.estimateCopyPopup.label.receiveUser')} <span className="red">*</span>
</div>
<div className="input-wrap">
<input
type="text"
className="input-light"
required
defaultValue={state?.charger}
onChange={(e) => {
setCopyReceiveUser(e.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="footer-btn-wrap">
<button type="button" className="btn-origin grey mr5" onClick={() => setEstimateCopyPopupOpen(false)}>
{getMessage('estimate.detail.estimateCopyPopup.close')}
</button>
<button
type="button"
className="btn-origin navy"
onClick={() => {
handleEstimateCopy(sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId)
}}
>
{getMessage('estimate.detail.estimateCopyPopup.copyBtn')}
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -14,6 +14,7 @@ import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitial
import { MENU } from '@/common/common'
import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/PanelBatchStatistics'
import { totalDisplaySelector } from '@/store/settingAtom'
import ImgLoad from '@/components/floor-plan/modal/ImgLoad'
export default function CanvasFrame() {
const canvasRef = useRef(null)
@ -76,6 +77,8 @@ export default function CanvasFrame() {
MENU.MODULE_CIRCUIT_SETTING.PLAN_ORIENTATION,
].includes(currentMenu) &&
totalDisplay && <PanelBatchStatistics />}
{/* 이미지 로드 팝업 */}
<ImgLoad />
</div>
)
}

View File

@ -1,6 +1,6 @@
'use client'
import { useEffect, useState } from 'react'
import { useContext, useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
@ -35,6 +35,8 @@ import { MENU } from '@/common/common'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { estimateState } from '@/store/floorPlanObjectAtom'
import DocDownOptionPop from '../estimate/popup/DocDownOptionPop'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
import EstimateCopyPop from '../estimate/popup/EstimateCopyPop'
export default function CanvasMenu(props) {
const { menuNumber, setMenuNumber } = props
@ -59,6 +61,7 @@ export default function CanvasMenu(props) {
const { handleEstimateSubmit } = useEstimateController()
const estimateRecoilState = useRecoilValue(estimateState)
const [estimatePopupOpen, setEstimatePopupOpen] = useState(false)
const [estimateCopyPopupOpen, setEstimateCopyPopupOpen] = useState(false)
const { getMessage } = useMessage()
const { currentCanvasPlan, saveCanvas } = usePlan()
@ -67,6 +70,7 @@ export default function CanvasMenu(props) {
const commonUtils = useRecoilValue(commonUtilsState)
const { commonFunctions } = useCommonUtils()
const SelectOption = [{ name: '瓦53A' }, { name: '瓦53A' }]
const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext)
const onClickNav = (menu) => {
setMenuNumber(menu.index)
@ -161,20 +165,6 @@ export default function CanvasMenu(props) {
})
}
/**
* 견적서 복사버튼
* (견적서 번호(estimateRecoilState.docNo) 생성된 이후 버튼 활성화 )
* T01관리자 계정 1차판매점에게만 제공
*/
const handleEstimateCopy = () => {
// console.log('estimateRecoilState::', estimateRecoilState)
//objectNo, planNo
console.log('복사')
console.log('물건정보+도면+견적서를 모두 복사')
console.log('견적서 가격은 정가를 표시')
}
useEffect(() => {
if (globalLocale === 'ko') {
setAppMessageState(KO)
@ -240,7 +230,10 @@ export default function CanvasMenu(props) {
<QSelectBox title={'瓦53A'} option={SelectOption} />
</div>
<div className="btn-from">
<button className="btn10"></button>
<button
className={`btn10 ${floorPlanState.refFileModalOpen && 'active'}`}
onClick={() => setFloorPlanState({ ...floorPlanState, refFileModalOpen: true })}
></button>
{/*<button className="btn04" onClick={() => setShowCanvasSettingModal(true)}></button>*/}
<button className="btn04" onClick={handlePopup}></button>
<button className="btn05"></button>
@ -279,7 +272,6 @@ export default function CanvasMenu(props) {
<span className="ico ico02"></span>
<span>{getMessage('plan.menu.estimate.save')}</span>
</button>
{/* {estimateRecoilState?.docNo != null && ( */}
<button
className="btn-frame gray ico-flx"
onClick={() => {
@ -289,17 +281,18 @@ export default function CanvasMenu(props) {
<span className="ico ico03"></span>
<span>{getMessage('plan.menu.estimate.reset')}</span>
</button>
{/* )} */}
<button
className="btn-frame gray ico-flx"
onClick={() => {
handleEstimateCopy()
}}
>
<span className="ico ico04"></span>
<span>{getMessage('plan.menu.estimate.copy')}</span>
</button>
{estimateRecoilState?.docNo !== null && (sessionState.storeId === 'T01' || sessionState.storeLvl === '1') && (
<button
className="btn-frame gray ico-flx"
onClick={() => {
setEstimateCopyPopupOpen(true)
}}
>
<span className="ico ico04"></span>
<span>{getMessage('plan.menu.estimate.copy')}</span>
</button>
)}
</div>
</>
)}
@ -324,6 +317,8 @@ export default function CanvasMenu(props) {
</div>
{/* 견적서(menuNumber=== 5) 상세화면인경우 문서다운로드 팝업 */}
{estimatePopupOpen && <DocDownOptionPop planNo={estimateRecoilState?.planNo} setEstimatePopupOpen={setEstimatePopupOpen} />}
{/* 견적서(menuNumber ===5)복사 팝업 */}
{estimateCopyPopupOpen && <EstimateCopyPop planNo={estimateRecoilState?.planNo} setEstimateCopyPopupOpen={setEstimateCopyPopupOpen} />}
</div>
)
}

View File

@ -1,3 +1,6 @@
import { useContext, useEffect } from 'react'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
import { useMessage } from '@/hooks/useMessage'
import { useRefFiles } from '@/hooks/common/useRefFiles'
import { usePlan } from '@/hooks/usePlan'
@ -20,9 +23,24 @@ export default function ImgLoad() {
} = useRefFiles()
const { currentCanvasPlan } = usePlan()
const { getMessage } = useMessage()
const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext)
const handleModal = () => {
setFloorPlanState({ ...floorPlanState, refFileModalOpen: false })
}
useEffect(() => {
console.log('🚀 ~ ImgLoad ~ floorPlanState.refFileModalOpen:', floorPlanState.refFileModalOpen)
console.log('🚀 ~ ImgLoad ~ currentCanvasPlan:', currentCanvasPlan)
}, [floorPlanState.refFileModalOpen])
useEffect(() => {
const refFileMethod = currentCanvasPlan?.mapPositionAddress === null ? '1' : '2'
setRefFileMethod(refFileMethod)
}, [currentCanvasPlan])
return (
<WithDraggable isShow={true}>
<WithDraggable isShow={floorPlanState.refFileModalOpen} pos={{ x: 1000, y: 200 }} handle=".modal">
<div className={`modal-pop-wrap r`}>
<div className="modal-head">
<h1 className="title">{getMessage('common.input.file')}</h1>
@ -32,7 +50,11 @@ export default function ImgLoad() {
<div className="img-flex-box">
<span className="normal-font mr10">サイズ調整と回転</span>
<label className="toggle-btn">
<input type="checkbox" />
<input
type="checkbox"
checked={floorPlanState.toggleRotate}
onChange={(e) => setFloorPlanState({ ...floorPlanState, toggleRotate: e.target.checked })}
/>
<span className="slider"></span>
</label>
</div>
@ -48,13 +70,18 @@ export default function ImgLoad() {
<span className="img-edit"></span>
ファイルの追加
</label>
<input type="file" id="img_file" style={{ display: 'none' }} onChange={(e) => handleRefFile(e.target.files[0])} />
<input
type="file"
id="img_file"
style={{ display: 'none' }}
onChange={refFileMethod === '1' ? (e) => handleRefFile(e.target.files[0]) : () => {}}
/>
</div>
<div className="img-name-wrap">
{/* <input type="text" className="input-origin al-l" defaultValue={'IMG_Name.PNG'} readOnly />
<button className="img-check"></button> */}
{currentCanvasPlan?.bgImageName === null ? (
<input type="text" className="input-origin al-l" defaultValue={''} value={refImage ? refImage.name : ''} readOnly />
<input type="text" className="input-origin al-l" value={refImage ? refImage.name : ''} readOnly />
) : (
<input type="text" className="input-origin al-l" value={currentCanvasPlan?.bgImageName} readOnly />
)}
@ -64,13 +91,13 @@ export default function ImgLoad() {
</div>
<div className="img-load-item">
<div className="d-check-radio pop">
<input type="radio" name="radio03" id="ra06" value={'2'} onChange={(e) => handleRefFileMethod(e)} checked={refFileMethod === '2'} />
<label htmlFor="ra06">アドレスを読み込む</label>
<input type="radio" name="radio03" id="ra07" value={'2'} onChange={(e) => handleRefFileMethod(e)} checked={refFileMethod === '2'} />
<label htmlFor="ra07">アドレスを読み込む</label>
</div>
<div className="img-flex-box for-address">
<input type="text" className="input-origin al-l mr10" placeholder={'住所入力'} />
<input type="text" className="input-origin al-l mr10" placeholder={'住所入力'} value={currentCanvasPlan?.mapPositionAddress} />
<div className="img-edit-wrap">
<button className="img-edit-btn" onClick={handleMapImageDown}>
<button className="img-edit-btn" onClick={refFileMethod === '2' ? handleMapImageDown : () => {}}>
完了
</button>
</div>
@ -80,7 +107,9 @@ export default function ImgLoad() {
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">完了</button>
<button className="btn-frame modal act" onClick={handleModal}>
完了
</button>
</div>
</div>
</div>

View File

@ -1,84 +0,0 @@
import { useMessage } from '@/hooks/useMessage'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { usePopup } from '@/hooks/usePopup'
import { useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom'
import { useState } from 'react'
export default function AuxiliaryCopy(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, pos = contextPopupPosition } = props
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [arrow1, setArrow1] = useState(null)
const [arrow2, setArrow2] = useState(null)
return (
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap xm mount`}>
<div className="modal-head">
<h1 className="title">{getMessage('modal.auxiliary.copy')} </h1>
<button className="modal-close" onClick={() => closePopup(id)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="grid-option-tit">{getMessage('modal.auxiliary.copy.info')}</div>
<div className="grid-option-wrap">
<div className="grid-option-box">
<div className="move-form">
<p className="mb5">{getMessage('length')}</p>
<div className="input-move-wrap mb5">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
</div>
<span>mm</span>
<div className="direction-move-wrap">
<button
className={`direction up ${arrow1 === '↑' ? 'act' : ''}`}
onClick={() => {
setArrow1('↑')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
}}
></button>
<button
className={`direction down ${arrow1 === '↓' ? 'act' : ''}`}
onClick={() => {
setArrow1('↓')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
}}
></button>
</div>
</div>
<div className="input-move-wrap">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
</div>
<span>mm</span>
<div className="direction-move-wrap">
<button
className={`direction left ${arrow2 === '←' ? 'act' : ''}`}
onClick={() => {
setArrow2('←')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }))
}}
></button>
<button
className={`direction right ${arrow2 === '→' ? 'act' : ''}`}
onClick={() => {
setArrow2('→')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
}}
></button>
</div>
</div>
</div>
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
</div>
</div>
</div>
</WithDraggable>
)
}

View File

@ -1,37 +1,62 @@
'use client'
import { useMessage } from '@/hooks/useMessage'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { usePopup } from '@/hooks/usePopup'
import { useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom'
import { usePopup } from '@/hooks/usePopup'
import { useState } from 'react'
import { currentObjectState } from '@/store/canvasAtom'
import { useLine } from '@/hooks/useLine'
import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing'
export default function AuxiliaryMove(props) {
export default function AuxiliaryEdit(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, pos = contextPopupPosition } = props
const { id, pos = contextPopupPosition, type } = props
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const { move, copy } = useAuxiliaryDrawing()
const [verticalSize, setVerticalSize] = useState('0')
const [horizonSize, setHorizonSize] = useState('0')
const [arrow1, setArrow1] = useState(null)
const [arrow2, setArrow2] = useState(null)
const { addLine, removeLine } = useLine()
const currentObject = useRecoilValue(currentObjectState)
const handleSave = () => {
if (type === 'copy') {
if (currentObject) {
copy(
currentObject,
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
)
}
} else {
move(
currentObject,
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
)
}
closePopup(id)
}
return (
<WithDraggable isShow={true} pos={pos}>
<div className={`modal-pop-wrap xm mount`}>
<div className="modal-head">
<h1 className="title">{getMessage('modal.auxiliary.move')}</h1>
<h1 className="title">{getMessage(type === 'copy' ? 'modal.auxiliary.copy' : 'modal.auxiliary.move')} </h1>
<button className="modal-close" onClick={() => closePopup(id)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="grid-option-tit">{getMessage('modal.auxiliary.move.info')}</div>
<div className="grid-option-tit">{getMessage(type === 'copy' ? 'modal.auxiliary.copy.info' : 'modal.auxiliary.move.info')}</div>
<div className="grid-option-wrap">
<div className="grid-option-box">
<div className="move-form">
<p className="mb5">{getMessage('length')}</p>
<div className="input-move-wrap mb5">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
<input type="text" className="input-origin" value={verticalSize} onChange={(e) => setVerticalSize(e.target.value)} />
</div>
<span>mm</span>
<div className="direction-move-wrap">
@ -53,7 +78,7 @@ export default function AuxiliaryMove(props) {
</div>
<div className="input-move-wrap">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
<input type="text" className="input-origin" value={horizonSize} onChange={(e) => setHorizonSize(e.target.value)} />
</div>
<span>mm</span>
<div className="direction-move-wrap">
@ -77,7 +102,9 @@ export default function AuxiliaryMove(props) {
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">{getMessage('modal.common.save')}</button>
<button className="btn-frame modal act" onClick={handleSave}>
{getMessage('modal.common.save')}
</button>
</div>
</div>
</div>

View File

@ -116,8 +116,6 @@ export default function StuffSearchCondition() {
schAddress: address,
schObjectName: objectName,
schDispCompanyName: dispCompanyName,
// schSelSaleStoreId: stuffSearch?.schSelSaleStoreId,
// schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId,
schSelSaleStoreId: schSelSaleStoreId,
schOtherSelSaleStoreId: otherSaleStoreId,
schReceiveUser: receiveUser,
@ -315,8 +313,10 @@ export default function StuffSearchCondition() {
} else {
//X
//
// setSchSelSaleStoreId('') // ..
setSchSelSaleStoreId(null)
setSchSelSaleStoreId('')
setOtherSaleStoreId('')
stuffSearch.schSelSaleStoreId = ''
stuffSearch.schOtherSelSaleStoreId = ''
//2
setOtherSaleStoreList([])

View File

@ -28,9 +28,7 @@ export default function StuffSubHeader({ type }) {
const param = {
pid: '1',
}
//
const url = `/floor-plan?${queryStringFormatter(param)}`
console.log(url)
router.push(url)
}
@ -107,7 +105,7 @@ export default function StuffSubHeader({ type }) {
<span>{getMessage('header.menus.management')}</span>
</li>
<li className="location-item">
<span>{getMessage('header.menus.management.newStuff')}</span>
<span>{getMessage('header.menus.management.detail')}</span>
</li>
</ul>
</>

View File

@ -41,6 +41,7 @@ export default function Simulator() {
//
const [chartData, setChartData] = useState([])
const data = {
labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
datasets: [
@ -118,13 +119,30 @@ export default function Simulator() {
//
const [pcsInfoList, setPcsInfoList] = useState([])
// list
const [hatsudenryouAll, setHatsudenryouAll] = useState([])
const [hatsudenryouAllSnow, setHatsudenryouAllSnow] = useState([])
const [hatsudenryouPeakcutAll, setHatsudenryouPeakcutAll] = useState([])
const [hatsudenryouPeakcutAllSnow, setHatsudenryouPeakcutAllSnow] = useState([])
const fetchObjectDetail = async (objectNo) => {
const apiUrl = `/api/pwrGnrSimulation/calculations?objectNo=${objectNo}&planNo=${plan?.id}`
const resultData = await get({ url: apiUrl })
if (resultData) {
setObjectDetail(resultData)
if (resultData.frcPwrGnrList) {
setChartData(resultData.frcPwrGnrList)
if (resultData.hatsudenryouAll) {
setHatsudenryouAll(resultData.hatsudenryouAll)
}
if (resultData.hatsudenryouAllSnow) {
setHatsudenryouAllSnow(resultData.hatsudenryouAllSnow)
}
if (resultData.hatsudenryouPeakcutAll) {
setHatsudenryouPeakcutAll(resultData.hatsudenryouPeakcutAll)
}
if (resultData.hatsudenryouPeakcutAllSnow) {
setHatsudenryouPeakcutAllSnow(resultData.hatsudenryouPeakcutAllSnow)
setChartData(resultData.hatsudenryouPeakcutAllSnow)
}
if (resultData.pcsList) {
setPcsInfoList(resultData.pcsList)
@ -148,6 +166,26 @@ export default function Simulator() {
})
}
// , list type
const [pwrGnrSimType, setPwrGnrSimType] = useState('D')
const handleChartChangeData = (type) => {
setPwrGnrSimType(type)
switch (type) {
case 'A':
setChartData(hatsudenryouAll)
break
case 'B':
setChartData(hatsudenryouAllSnow)
break
case 'C':
setChartData(hatsudenryouPeakcutAll)
break
case 'D':
setChartData(hatsudenryouPeakcutAllSnow)
break
}
}
return (
<div className="sub-content estimate">
<div className="sub-content-inner">
@ -176,7 +214,7 @@ export default function Simulator() {
{/* 연간예측발전량 */}
<div className="estimate-box">
<div className="estimate-tit">{getMessage('simulator.title.sub4')}</div>
<div className="estimate-name">{objectDetail.anlFrcsGnrt ? convertNumberToPriceDecimal(objectDetail.anlFrcsGnrt) : ''}</div>
<div className="estimate-name">{chartData[chartData.length - 1]}</div>
</div>
</div>
<div className="estimate-list-wrap">
@ -208,6 +246,22 @@ export default function Simulator() {
<div className="chart-inner">
<div className="sub-table-box">
<div className="chart-box">
{/* chart */}
<div className="select-wrap">
<select
style={{ width: '30%' }}
className="select-light"
defaultValue={`D`}
onChange={(e) => {
handleChartChangeData(e.target.value)
}}
>
<option value={`A`}>積雪考慮なし(ピークカットなし発電量)</option>
<option value={`B`}>積雪考慮なし(ピークカットあり発電量)</option>
<option value={`C`}>積雪考慮あり(ピークカットなし発電量)</option>
<option value={`D`}>積雪考慮あり(ピークカットあり発電量)</option>
</select>
</div>
<Bar ref={chartRef} data={data} options={options} />
</div>
<div className="table-box-title-wrap">
@ -239,7 +293,7 @@ export default function Simulator() {
{chartData.length > 0 ? (
<tr>
{chartData.map((data) => (
<td key={data}>{convertNumberToPriceDecimal(data)}</td>
<td key={data}>{data}</td>
))}
</tr>
) : (

View File

@ -1,8 +1,30 @@
import { menuNumberState } from '@/store/menuAtom'
import { useRecoilState } from 'recoil'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useEffect } from 'react'
import { canvasState } from '@/store/canvasAtom'
import { usePolygon } from '@/hooks/usePolygon'
import { POLYGON_TYPE } from '@/common/common'
export const useCanvasMenu = () => {
const [menuNumber, setMenuNumber] = useRecoilState(menuNumberState)
const canvas = useRecoilValue(canvasState)
const { drawDirectionArrow } = usePolygon()
useEffect(() => {
/*
* 모듈,회로 구성을 벗어나면 방향 표시 초기화 필요
* */
if (!canvas) return
if (![4, 5].includes(menuNumber)) {
canvas
.getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.ROOF)
.forEach((obj) => {
obj.set('moduleCompass', null)
drawDirectionArrow(obj)
})
}
}, [menuNumber])
return {
menuNumber,

View File

@ -6,27 +6,11 @@ import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom
import { isObjectNotEmpty } from '@/util/common-utils'
import { SessionContext } from '@/app/SessionProvider'
import { useMessage } from '@/hooks/useMessage'
const reducer = (prevState, nextState) => {
return { ...prevState, ...nextState }
}
import { useRouter } from 'next/navigation'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
// Constants
const ESTIMATE_API_ENDPOINT = '/api/estimates' // API 엔드포인트 정의
const defaultEstimateData = {
estimateDate: new Date(), //견적일
charger: '', //담당자
objectName: '', //안건명
objectNameOmit: '', //경칭코드
estimateType: '', //주문분류
remarks: '', //비고
estimateOption: '', //견적특이사항
itemList: [],
fileList: [],
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
priceCd: '',
}
const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의
// Helper functions
const updateItemInList = (itemList, dispOrder, updates) => {
@ -34,6 +18,7 @@ const updateItemInList = (itemList, dispOrder, updates) => {
}
export const useEstimateController = (planNo) => {
const router = useRouter()
const { session } = useContext(SessionContext)
const globalLocaleState = useRecoilValue(globalLocaleStore)
const objectRecoil = useRecoilValue(floorPlanObjectState)
@ -41,31 +26,32 @@ export const useEstimateController = (planNo) => {
const { getMessage } = useMessage()
const { get, post, promisePost } = useAxios(globalLocaleState)
const { promiseGet, post, promisePost } = useAxios(globalLocaleState)
const [isLoading, setIsLoading] = useState(false)
const [state, setState] = useReducer(reducer, defaultEstimateData)
const { estimateContextState, setEstimateContextState } = useContext(FloorPlanContext)
useEffect(() => {
if (!isLoading) {
if (planNo && !isLoading) {
if (objectRecoil.floorPlanObjectNo && planNo) {
fetchSetting()
fetchSetting(objectRecoil.floorPlanObjectNo, planNo)
}
}
}, [])
// 상세 조회
const fetchSetting = async () => {
const fetchSetting = async (objectNo, planNo) => {
try {
await get({ url: `/api/estimate/${objectRecoil.floorPlanObjectNo}/${planNo}/detail` }).then((res) => {
if (isObjectNotEmpty(res)) {
if (res.itemList.length > 0) {
res.itemList.map((item) => {
item.delFlg = '0'
})
await promiseGet({ url: `/api/estimate/${objectNo}/${planNo}/detail` }).then((res) => {
if (res.status === 200) {
if (isObjectNotEmpty(res.data)) {
if (res.data.itemList.length > 0) {
res.data.itemList.map((item) => {
item.delFlg = '0'
})
}
setEstimateContextState(res.data)
}
setState(res)
}
})
setIsLoading(true)
@ -76,18 +62,17 @@ export const useEstimateController = (planNo) => {
}
const updateItem = (dispOrder, updates) => {
setState({
itemList: updateItemInList(state.itemList, dispOrder, updates),
setEstimateContextState({
itemList: updateItemInList(estimateContextState.itemList, dispOrder, updates),
})
}
const addItem = () => {
// const newItemDispOrder = (Math.max(...state.itemList.map((item) => item.dispOrder)) / 100 + 1) * 100
let newItemDispOrder = Math.max(...state.itemList.map((item) => item.dispOrder))
let newItemDispOrder = Math.max(...estimateContextState.itemList.map((item) => item.dispOrder))
newItemDispOrder = (Math.floor(newItemDispOrder / 100) + 1) * 100
setState({
setEstimateContextState({
itemList: [
...state.itemList,
...estimateContextState.itemList,
{
objectNo: objectRecoil.floorPlanObjectNo,
planNo: planNo,
@ -109,8 +94,8 @@ export const useEstimateController = (planNo) => {
}
useEffect(() => {
setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd: session.custCd })
}, [state])
setEstimateData({ ...estimateContextState, userId: session.userId, sapSalesStoreCd: session.custCd })
}, [estimateContextState])
// 첨부파일 다운로드
const handleEstimateFileDownload = async (originFile) => {
@ -141,24 +126,41 @@ export const useEstimateController = (planNo) => {
const handleEstimateSubmit = async () => {
//0. 필수체크
let flag = true
if (estimateData.charger.trim().length === 0) {
flag = false
return alert(getMessage('estimate.detail.save.requiredCharger'))
}
if (estimateData.objectName.trim().length === 0) {
flag = false
return alert(getMessage('estimate.detail.save.requiredObjectName'))
}
if (isNaN(Date.parse(estimateData.estimateDate))) {
flag = false
return alert(getMessage('estimate.detail.save.requiredEstimateDate'))
}
// console.log('첨부파일:::::', estimateData.fileList)
//첨부파일을 첨부안했는데
//아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼
let fileFlg = true
if (estimateData.fileList.length < 1) {
if (estimateData.itemList.length > 1) {
estimateData.itemList.map((row) => {
if (row.fileUploadFlg === '1') {
if (estimateData.fileFlg === '0') {
alert(getMessage('estimate.detail.save.requiredMsg'))
flag = false
if (fileFlg) {
if (estimateData.fileFlg === '0') {
fileFlg = false
return alert(getMessage('estimate.detail.save.requiredFileUpload'))
}
}
}
})
}
}
if (flag) {
if (flag && fileFlg) {
//1. 첨부파일 저장시작
const formData = new FormData()
formData.append('file', estimateData.fileList)
@ -184,24 +186,62 @@ export const useEstimateController = (planNo) => {
}
console.log('최종 정보::;', estimateData)
console.log('최종 남은 아이템정보:::', estimateData.itemList)
//2. 상세데이터 저장
// return
await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => {
if (res) {
if (res.status === 201) {
alert(getMessage('estimate.detail.save.alertMsg'))
//어디로 보낼지
fetchSetting(objectRecoil.floorPlanObjectNo, estimateData.planNo)
}
})
}
}
/**
* 견적서 복사버튼
* (견적서 번호(estimateData.docNo) 생성된 이후 버튼 활성화 )
* T01관리자 계정 1차판매점에게만 제공
*/
const handleEstimateCopy = async (sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId) => {
if (saleStoreId === '') {
return alert(getMessage('estimate.detail.productFeaturesPopup.requiredStoreId'))
}
if (copyReceiveUser.trim().length === 0) {
return alert(getMessage('estimate.detail.productFeaturesPopup.requiredReceiveUser'))
}
const params = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
objectNo: objectRecoil.floorPlanObjectNo,
planNo: sendPlanNo,
copySaleStoreId: otherSaleStoreId ? otherSaleStoreId : saleStoreId,
copyReceiveUser: copyReceiveUser,
userId: session.userId,
}
// return
await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }).then((res) => {
if (res.status === 201) {
if (isObjectNotEmpty(res.data)) {
let newObjectNo = res.data.objectNo
alert(getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'))
router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false })
}
}
})
}
return {
state,
setState,
estimateContextState,
setEstimateContextState,
updateItem,
addItem,
handleEstimateSubmit,
fetchSetting,
handleEstimateFileDownload,
handleEstimateCopy,
}
}

View File

@ -47,6 +47,8 @@ export function useModuleBasicSetting() {
flowDirection: roof.direction,
})
setupSurface.setViewLengthText(false)
canvas.add(setupSurface)
//지붕면 선택 금지
roof.set({
@ -567,6 +569,7 @@ export function useModuleBasicSetting() {
lineCol: col,
lineRow: row,
})
tempModule.setViewLengthText(false)
canvas?.add(tempModule)
moduleSetupArray.push(tempModule)
}
@ -596,6 +599,106 @@ export function useModuleBasicSetting() {
})
setModuleIsSetup(moduleSetupArray)
console.log(calculateForApi(moduleSetupArray))
}
const calculateForApi = (moduleSetupArray) => {
const centerPoints = []
moduleSetupArray.forEach((module, index) => {
module.tempIndex = index
const { x, y } = module.getCenterPoint()
const { width, height } = module
centerPoints.push({ x, y, width, height })
const circle = new fabric.Circle({
radius: 5,
fill: 'red',
name: 'redCircle',
left: x - 5,
top: y - 5,
index: index,
selectable: false,
})
canvas.add(circle)
})
//완전 노출 하면
let exposedBottom = 0
// 반 노출 하면
let exposedHalfBottom = 0
// 완전 노출 상면
let exposedTop = 0
//반 노출 상면
let exposedHalfTop = 0
// 완전 접면
let touchDimension = 0
//반접면
let halfTouchDimension = 0
// 노출하면 체크
centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint
// centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인
const bottomCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y + height)) < 2)
if (bottomCell.length === 1) {
touchDimension++
return
}
// 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다.
const leftBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - (x - width / 2)) < 2 && Math.abs(centerPoint.y - (y + height)) < 2,
).length
const rightBottomCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - (x + width / 2)) < 2 && Math.abs(centerPoint.y - (y + height)) < 2,
).length
if (leftBottomCnt + rightBottomCnt === 2) {
touchDimension++
return
}
if (leftBottomCnt + rightBottomCnt === 1) {
halfTouchDimension++
exposedHalfBottom++
return
}
if (leftBottomCnt + rightBottomCnt === 0) {
exposedBottom++
return
}
})
// 노출상면 체크
centerPoints.forEach((centerPoint, index) => {
const { x, y, width, height } = centerPoint
const topCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y - height)) < 2)
if (topCell.length === 1) {
return
}
const leftTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - (x - width) < 2) && Math.abs(centerPoint.y - (y - height)) < 2,
).length
const rightTopCnt = centerPoints.filter(
(centerPoint) => Math.abs(centerPoint.x - (x + width / 2) < 2) && Math.abs(centerPoint.y - (y - height)) < 2,
).length
if (leftTopCnt + rightTopCnt === 1) {
exposedHalfTop++
return
}
if (leftTopCnt + rightTopCnt === 0) {
exposedTop++
return
}
})
return {
exposedBottom,
exposedHalfBottom,
exposedTop,
exposedHalfTop,
touchDimension,
halfTouchDimension,
}
}
const coordToTurfPolygon = (points) => {

View File

@ -15,14 +15,12 @@ import {
outerLineLength2State,
outerLineTypeState,
} from '@/store/outerLineAtom'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, isPointOnLine, polygonToTurfPolygon } from '@/util/canvas-util'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, isPointOnLine } from '@/util/canvas-util'
import { fabric } from 'fabric'
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
import { useSwal } from '@/hooks/useSwal'
import { booleanPointInPolygon } from '@turf/turf'
import { usePopup } from '@/hooks/usePopup'
import { calculateAngle, isSamePoint } from '@/util/qpolygon-utils'
import { QPolygon } from '@/components/fabric/QPolygon'
import { POLYGON_TYPE } from '@/common/common'
// 보조선 작성
@ -122,6 +120,38 @@ export function useAuxiliaryDrawing(id) {
setOuterLineDiagonalLength(0)
}
const move = (object, x, y) => {
const line = copy(object, x, y)
canvas.remove(object)
canvas.setActiveObject(line)
}
const copy = (object, x, y) => {
return addLine([object.x1 + x, object.y1 + y, object.x2 + x, object.y2 + y], {
stroke: 'red',
strokeWidth: 1,
selectable: true,
name: 'auxiliaryLine',
})
}
const addBisectorLine = (target) => {
const slope = (target.y2 - target.y1) / (target.x2 - target.x1)
const bisectorSlope = -1 / slope
const length = target.length
const dx = length / Math.sqrt(1 + bisectorSlope * bisectorSlope)
const dy = bisectorSlope * dx
const endX = (target.x1 + target.x2) / 2
const endY = (target.y1 + target.y2) / 2
addLine([dx, dy, endX, endY], {
stroke: 'red',
strokeWidth: 1,
selectable: true,
name: 'auxiliaryLine',
})
}
const keydown = {
outerLine: (e) => {
if (mousePointerArr.current.length === 0) {
@ -130,7 +160,7 @@ export function useAuxiliaryDrawing(id) {
// 포커스가 length1에 있지 않으면 length1에 포커스를 줌
const activeElem = document.activeElement
if (activeElem !== length1Ref.current) {
length1Ref.current.focus()
length1Ref?.current?.focus()
}
const key = e.key
@ -180,7 +210,7 @@ export function useAuxiliaryDrawing(id) {
const activeElem = document.activeElement
if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) {
length1Ref.current.focus()
length1Ref?.current?.focus()
}
switch (key) {
@ -871,5 +901,8 @@ export function useAuxiliaryDrawing(id) {
handleRollback,
buttonAct,
setButtonAct,
move,
copy,
addBisectorLine,
}
}

View File

@ -1,5 +1,5 @@
import { useRecoilValue } from 'recoil'
import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useState } from 'react'
import { setSurfaceShapePattern } from '@/util/canvas-util'
import { useSwal } from '@/hooks/useSwal'
@ -10,6 +10,9 @@ import { POLYGON_TYPE } from '@/common/common'
import { v4 as uuidv4 } from 'uuid'
import ActualSizeSetting from '@/components/floor-plan/modal/roofAllocation/ActualSizeSetting'
import { useMessage } from '@/hooks/useMessage'
import useMenu from '@/hooks/common/useMenu'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { menuTypeState } from '@/store/menuAtom'
// 지붕면 할당
export function useRoofAllocationSetting(id) {
@ -21,6 +24,8 @@ export function useRoofAllocationSetting(id) {
const { getMessage } = useMessage()
const currentObject = useRecoilValue(currentObjectState)
const { swalFire } = useSwal()
const { setMenuNumber } = useCanvasMenu()
const setMenuType = useSetRecoilState(menuTypeState)
const roofMaterials = [
{
id: 'A',
@ -132,6 +137,9 @@ export function useRoofAllocationSetting(id) {
setValues(values.filter((value) => value.id !== id))
}
const { handleMenu } = useMenu()
const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState)
// 선택한 지붕재로 할당
const handleSave = () => {
// 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정
@ -213,6 +221,8 @@ export function useRoofAllocationSetting(id) {
})
setEditingLines([])
closeAll()
setMenuNumber(3)
setMenuType('surface')
}
const setLineSize = (id, size) => {

View File

@ -1,8 +1,7 @@
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { currentMenuState, currentObjectState } from '@/store/canvasAtom'
import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useState } from 'react'
import { MENU } from '@/common/common'
import AuxiliaryMove from '@/components/floor-plan/modal/auxiliary/AuxiliaryMove'
import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize'
import { usePopup } from '@/hooks/usePopup'
import { v4 as uuidv4 } from 'uuid'
@ -11,7 +10,7 @@ import GridCopy from '@/components/floor-plan/modal/grid/GridCopy'
import ColorPickerModal from '@/components/common/color-picker/ColorPickerModal'
import { gridColorState } from '@/store/gridAtom'
import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom'
import AuxiliaryCopy from '@/components/floor-plan/modal/auxiliary/AuxiliaryCopy'
import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit'
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
import RoofMaterialSetting from '@/components/floor-plan/modal/object/RoofMaterialSetting'
import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset'
@ -34,8 +33,10 @@ import CircuitNumberEdit from '@/components/floor-plan/modal/module/CircuitNumbe
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
import { fontSelector, globalFontAtom } from '@/store/fontAtom'
import { useLine } from '@/hooks/useLine'
export function useContextMenu() {
const canvas = useRecoilValue(canvasState)
const currentMenu = useRecoilValue(currentMenuState) // 현재 메뉴
const setContextPopupPosition = useSetRecoilState(contextPopupPositionState) // 현재 메뉴
const [contextMenu, setContextMenu] = useRecoilState(contextMenuListState) // 메뉴.object 별 context menu
@ -53,6 +54,7 @@ export function useContextMenu() {
const { moveObjectBatch } = useObjectBatch({})
const { moveSurfaceShapeBatch } = useSurfaceShapeBatch()
const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom)
const { addLine, removeLine } = useLine()
const commonTextFont = useRecoilValue(fontSelector('commonText'))
const currentMenuSetting = () => {
@ -130,22 +132,57 @@ export function useContextMenu() {
id: 'auxiliaryMove',
name: `${getMessage('contextmenu.auxiliary.move')}(M)`,
shortcut: ['m', 'M'],
component: <AuxiliaryMove id={popupId} />,
component: <AuxiliaryEdit id={popupId} type={'move'} />,
},
{
id: 'auxiliaryCopy',
name: `${getMessage('contextmenu.auxiliary.copy')}(C)`,
shortcut: ['c', 'C'],
component: <AuxiliaryCopy id={popupId} />,
component: <AuxiliaryEdit id={popupId} type={'copy'} />,
},
{
id: 'auxiliaryRemove',
shortcut: ['d', 'D'],
name: `${getMessage('contextmenu.auxiliary.remove')}(D)`,
fn: () => {
canvas.remove(currentObject)
canvas.discardActiveObject()
},
},
{
id: 'auxiliaryVerticalBisector',
name: getMessage('contextmenu.auxiliary.vertical.bisector'),
fn: () => {
const slope = (currentObject.y2 - currentObject.y1) / (currentObject.x2 - currentObject.x1)
const length = currentObject.length
let startX, startY, endX, endY
if (slope === 0) {
startX = endX = (currentObject.x1 + currentObject.x2) / 2
startY = currentObject.y2 - length / 2
endY = currentObject.y2 + length / 2
} else if (slope === Infinity) {
startX = currentObject.x1 - length / 2
startY = endY = (currentObject.y1 + currentObject.y2) / 2
endX = currentObject.x1 + length / 2
} else {
const bisectorSlope = -1 / slope
const dx = length / 2 / Math.sqrt(1 + bisectorSlope * bisectorSlope)
const dy = bisectorSlope * dx
startX = (currentObject.x1 + currentObject.x2) / 2 + dx
startY = (currentObject.y1 + currentObject.y2) / 2 + dy
endX = (currentObject.x1 + currentObject.x2) / 2 - dx
endY = (currentObject.y1 + currentObject.y2) / 2 - dy
}
addLine([startX, startY, endX, endY], {
stroke: 'red',
strokeWidth: 1,
selectable: true,
name: 'auxiliaryLine',
})
},
},
{
id: 'auxiliaryCut',
@ -472,6 +509,7 @@ export function useContextMenu() {
{
id: 'removeAll',
name: getMessage('contextmenu.remove.all'),
fn: () => {},
},
],
])

View File

@ -4,6 +4,7 @@
"header.menus.home": "ホームへv",
"header.menus.management": "物品及び図面管理",
"header.menus.management.newStuff": "新規 物件 登録",
"header.menus.management.detail": "物件詳細",
"header.menus.management.stuffList": "物件の状況",
"header.menus.community": "コミュニティ",
"header.menus.community.notice": "お知らせ",
@ -160,7 +161,7 @@
"plan.menu.estimate.docDown": "文書のダウンロード",
"plan.menu.estimate.save": "保存",
"plan.menu.estimate.reset": "初期化",
"plan.menu.estimate.copy": "コピー",
"plan.menu.estimate.copy": "見積書のコピー",
"plan.menu.simulation": "発展シミュレーション",
"plan.menu.simulation.excel": "Excel",
"plan.menu.simulation.pdf": "PDF",
@ -822,6 +823,8 @@
"estimate.detail.objectName": "案件名",
"estimate.detail.objectRemarks": "メモ",
"estimate.detail.estimateType": "注文分類",
"estimate.detail.estimateType.yjss": "住宅PKG",
"estimate.detail.estimateType.yjod": "積上げ( YJOD )",
"estimate.detail.roofCns": "屋根材・仕様施工",
"estimate.detail.remarks": "備考",
"estimate.detail.fileFlg": "後日資料提出",
@ -838,8 +841,6 @@
"estimate.detail.sepcialEstimateProductInfo.pkgUnitPrice": "住宅PKG単価 (W)",
"estimate.detail.sepcialEstimateProductInfo.pkgWeight": "PKG容量 (Kw)",
"estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG金額",
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
"estimate.detail.header.showPrice": "価格表示",
"estimate.detail.header.unitPrice": "定価",
"estimate.detail.showPrice.pricingBtn": "Pricing",
@ -875,11 +876,24 @@
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "含まない",
"estimate.detail.docPopup.close": "閉じる",
"estimate.detail.docPopup.docDownload": "文書のダウンロード",
"estimate.detail.estimateCopyPopup.title": "見積もり",
"estimate.detail.estimateCopyPopup.explane": "見積書をコピーする販売店を設定します。見積もりは定価にコピーされます.",
"estimate.detail.estimateCopyPopup.label.saleStoreId": "一次販売店名 / ID",
"estimate.detail.estimateCopyPopup.label.otherSaleStoreId": "二次販売店名 / ID",
"estimate.detail.estimateCopyPopup.label.receiveUser": "担当者",
"estimate.detail.estimateCopyPopup.close": "閉じる",
"estimate.detail.estimateCopyPopup.copyBtn": "見積もり",
"estimate.detail.estimateCopyPopup.copy.alertMessage": "見積書がコピーされました. コピーした商品情報に移動します.",
"estimate.detail.productFeaturesPopup.title": "製品特異事項",
"estimate.detail.productFeaturesPopup.close": "閉じる",
"estimate.detail.productFeaturesPopup.requiredStoreId": "一次販売店は必須です.",
"estimate.detail.productFeaturesPopup.requiredReceiveUser": "担当者は必須です.",
"estimate.detail.save.alertMsg": "保存されている見積書で製品を変更した場合、図面や回路には反映されません.",
"estimate.detail.save.requiredMsg": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください.",
"estimate.detail.save.requiredFileUpload": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください.",
"estimate.detail.save.requiredItem": "製品は1つ以上登録する必要があります.",
"estimate.detail.save.requiredCharger": "担当者は必須です.",
"estimate.detail.save.requiredObjectName": "案件名は必須です.",
"estimate.detail.save.requiredEstimateDate": "見積日は必須です.",
"estimate.detail.reset.confirmMsg": "保存した見積書情報が初期化され、図面情報が反映されます。本当に初期化しますか?",
"simulator.title.sub1": "物件番号",
"simulator.title.sub2": "作成日",

View File

@ -4,6 +4,7 @@
"header.menus.home": "Home",
"header.menus.management": "물건 및 도면 관리",
"header.menus.management.newStuff": "신규 물건 등록",
"header.menus.management.detail": "물건 상세",
"header.menus.management.stuffList": "물건 현황",
"header.menus.community": "커뮤니티",
"header.menus.community.notice": "공지",
@ -164,7 +165,7 @@
"plan.menu.estimate.docDown": "문서 다운로드",
"plan.menu.estimate.save": "저장",
"plan.menu.estimate.reset": "초기화",
"plan.menu.estimate.copy": "복사",
"plan.menu.estimate.copy": "견적서 복사",
"plan.menu.simulation": "발전 시뮬레이션",
"plan.menu.simulation.excel": "Excel",
"plan.menu.simulation.pdf": "PDF",
@ -832,6 +833,8 @@
"estimate.detail.objectName": "안건명",
"estimate.detail.objectRemarks": "메모",
"estimate.detail.estimateType": "주문분류",
"estimate.detail.estimateType.yjss": "住宅PKG",
"estimate.detail.estimateType.yjod": "積上げ( YJOD )",
"estimate.detail.roofCns": "지붕재・사양시공",
"estimate.detail.remarks": "비고",
"estimate.detail.fileFlg": "후일자료제출",
@ -848,8 +851,6 @@
"estimate.detail.sepcialEstimateProductInfo.pkgUnitPrice": "주택PKG단가 (W)",
"estimate.detail.sepcialEstimateProductInfo.pkgWeight": "PKG 용량 (Kw)",
"estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG 금액",
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈용량 * 수량)÷100",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
"estimate.detail.header.showPrice": "가격표시",
"estimate.detail.header.unitPrice": "정가",
"estimate.detail.showPrice.pricingBtn": "Pricing",
@ -885,11 +886,24 @@
"estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "미포함",
"estimate.detail.docPopup.close": "닫기",
"estimate.detail.docPopup.docDownload": "문서 다운로드",
"estimate.detail.estimateCopyPopup.title": "견적복사",
"estimate.detail.estimateCopyPopup.explane": "견적서를 복사할 판매점을 설정하십시오. 견적서는 정가로 복사됩니다.",
"estimate.detail.estimateCopyPopup.label.saleStoreId": "1차 판매점명 / ID",
"estimate.detail.estimateCopyPopup.label.otherSaleStoreId": "2차 판매점명 / ID",
"estimate.detail.estimateCopyPopup.label.receiveUser": "담당자",
"estimate.detail.estimateCopyPopup.close": "닫기",
"estimate.detail.estimateCopyPopup.copyBtn": "견적복사",
"estimate.detail.estimateCopyPopup.copy.alertMessage": "견적서가 복사되었습니다. 복사된 물건정보로 이동합니다.",
"estimate.detail.productFeaturesPopup.title": "제품특이사항",
"estimate.detail.productFeaturesPopup.close": "닫기",
"estimate.detail.productFeaturesPopup.requiredStoreId": "1차 판매점은 필수값 입니다.",
"estimate.detail.productFeaturesPopup.requiredReceiveUser": "담당자는 필수값 입니다.",
"estimate.detail.save.alertMsg": "저장되었습니다. 견적서에서 제품을 변경할 경우, 도면 및 회로에 반영되지 않습니다.",
"estimate.detail.save.requiredMsg": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.",
"estimate.detail.save.requiredFileUpload": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.",
"estimate.detail.save.requiredItem": "제품은 1개이상 등록해야 됩니다.",
"estimate.detail.save.requiredCharger": "담당자는 필수값 입니다.",
"estimate.detail.save.requiredObjectName": "안건명은 필수값 입니다.",
"estimate.detail.save.requiredEstimateDate": "견적일은 필수값 입니다.",
"estimate.detail.reset.confirmMsg": "저장된 견적서 정보가 초기화되고, 도면정보가 반영됩니다. 정말로 초기화 하시겠습니까?",
"simulator.title.sub1": "물건번호",
"simulator.title.sub2": "작성일",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3773
yarn.lock

File diff suppressed because it is too large Load Diff