1237 lines
49 KiB
JavaScript
1237 lines
49 KiB
JavaScript
'use client'
|
|
|
|
import { useEffect, useState, useContext } from 'react'
|
|
import { useRecoilValue } from 'recoil'
|
|
import { floorPlanObjectState } from '@/store/floorPlanObjectAtom'
|
|
import { useMessage } from '@/hooks/useMessage'
|
|
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
|
import SingleDatePicker from '../common/datepicker/SingleDatePicker'
|
|
import EstimateFileUploader from './EstimateFileUploader'
|
|
import { useAxios } from '@/hooks/useAxios'
|
|
import { globalLocaleStore } from '@/store/localeAtom'
|
|
import { isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
|
|
import dayjs from 'dayjs'
|
|
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
|
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
|
import { SessionContext } from '@/app/SessionProvider'
|
|
import Select, { components } from 'react-select'
|
|
import { convertNumberToPriceDecimal } from '@/util/common-utils'
|
|
import ProductFeaturesPop from './popup/ProductFeaturesPop'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
export default function Estimate({ params }) {
|
|
const fixedKey = 'itemKey'
|
|
const [itemChangeYn, setItemChangeYn] = useState(false)
|
|
const { session } = useContext(SessionContext)
|
|
const [objectNo, setObjectNo] = useState('') //물건번호
|
|
const [planNo, setPlanNo] = useState('') //플랜번호
|
|
|
|
const [files, setFiles] = useState([]) // 보내는 첨부파일
|
|
const [originFiles, setOriginFiles] = useState([]) //기존 첨부파일
|
|
|
|
const [showPriceCd, setShowPriceCd] = useState('')
|
|
|
|
const [showContentCode, setShowContentCode] = useState('ATTR001')
|
|
|
|
const [productFeaturesPopupOpen, setProductFeaturesPopupOpen] = useState(false) //견적특이사항 팝업
|
|
const [showProductFeatureData, setShowProductFeatureData] = useState([]) //팝업에 보여줄 견적특이사항 데이터
|
|
|
|
const [selection, setSelection] = useState(new Set())
|
|
//견적특이사항 접고 펼치기
|
|
const [hidden, setHidden] = useState(false)
|
|
|
|
//아이템 자동완성 리스트
|
|
const [displayItemList, setDisplayItemList] = useState([])
|
|
|
|
//공통코드
|
|
const { findCommonCode } = useCommonCode()
|
|
const [honorificCodeList, setHonorificCodeList] = useState([]) //경칭 공통코드
|
|
|
|
const [storePriceList, setStorePriceList] = useState([]) //가격표시 option
|
|
|
|
const [startDate, setStartDate] = useState(new Date())
|
|
const singleDatePickerProps = {
|
|
startDate,
|
|
setStartDate,
|
|
}
|
|
|
|
const objectRecoil = useRecoilValue(floorPlanObjectState)
|
|
|
|
//견적서 상세데이터
|
|
const { estimateContextState, setEstimateContextState, addItem, handleEstimateFileDownload } = useEstimateController(params.pid)
|
|
|
|
//견적특이사항 List
|
|
const [specialNoteList, setSpecialNoteList] = useState([])
|
|
|
|
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
|
const { get, promisePost } = useAxios(globalLocaleState)
|
|
|
|
const { getMessage } = useMessage()
|
|
|
|
const { setMenuNumber } = useCanvasMenu()
|
|
|
|
//새로 추가한 첨부파일 props
|
|
const fileUploadProps = {
|
|
uploadFiles: files,
|
|
setUploadFiles: setFiles,
|
|
}
|
|
|
|
useEffect(() => {
|
|
setMenuNumber(5)
|
|
setObjectNo(objectRecoil.floorPlanObjectNo)
|
|
setPlanNo(params.pid)
|
|
|
|
// 공통코드
|
|
const code1 = findCommonCode(200800)
|
|
if (code1 != null) {
|
|
setHonorificCodeList(code1)
|
|
}
|
|
|
|
//아이템 자동완성 목록 가져오기
|
|
const param = {
|
|
saleStoreId: session.storeId,
|
|
}
|
|
const apiUrl = `/api/display-item/item-list?${queryStringFormatter(param)}`
|
|
get({ url: apiUrl }).then((res) => {
|
|
if (res.length > 0) {
|
|
setDisplayItemList(res)
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
//견적특이사항 API호출
|
|
//여러개 선택하면 구분자로 (、)
|
|
let url = `/api/estimate/special-note-list`
|
|
get({ url: url }).then((res) => {
|
|
if (isNotEmptyArray(res)) {
|
|
if (estimateContextState?.estimateOption) {
|
|
res.map((row) => {
|
|
let estimateOption = estimateContextState?.estimateOption?.split('、')
|
|
row.text = false
|
|
estimateOption.map((row2) => {
|
|
if (row2 === row.code) {
|
|
row.text = true
|
|
}
|
|
})
|
|
})
|
|
setSpecialNoteList(res)
|
|
}
|
|
}
|
|
})
|
|
}, [estimateContextState?.estimateOption])
|
|
|
|
//견적일 set
|
|
useEffect(() => {
|
|
let estimateDate = dayjs(startDate).format('YYYY-MM-DD')
|
|
setEstimateContextState({ estimateDate: estimateDate })
|
|
}, [startDate])
|
|
|
|
//API데이터로 견적일 셋팅
|
|
useEffect(() => {
|
|
setStartDate(estimateContextState?.estimateDate)
|
|
}, [estimateContextState?.estimateDate])
|
|
|
|
useEffect(() => {
|
|
//선택된 견적특이사항 setEstimateContextState
|
|
if (isNotEmptyArray(specialNoteList)) {
|
|
const liveCheckedData = specialNoteList.filter((row) => row.text === true)
|
|
|
|
const data = []
|
|
for (let ele of liveCheckedData) {
|
|
data.push(ele.code)
|
|
}
|
|
|
|
const newData = data.join('、')
|
|
setEstimateContextState({ estimateOption: newData })
|
|
}
|
|
}, [specialNoteList])
|
|
|
|
// 견적특이사항 remark 보여주기
|
|
const settingShowContent = (code, event) => {
|
|
setShowContentCode(code)
|
|
event.stopPropagation()
|
|
}
|
|
|
|
// 추가한 첨부파일 estimateContextState에 넣기
|
|
useEffect(() => {
|
|
if (isNotEmptyArray(files)) {
|
|
files.map((row) => {
|
|
setEstimateContextState({ fileList: row.data })
|
|
})
|
|
} else {
|
|
setEstimateContextState({ fileList: [] })
|
|
}
|
|
}, [files])
|
|
|
|
//상세에서 내려온 첨부파일 set 만들기
|
|
useEffect(() => {
|
|
if (isNotEmptyArray(estimateContextState.fileList)) {
|
|
setOriginFiles(estimateContextState.fileList)
|
|
}
|
|
}, [estimateContextState?.fileList])
|
|
|
|
// 기존첨부파일 삭제
|
|
const deleteOriginFile = async (objectNo, no) => {
|
|
const delParams = {
|
|
userId: session.userId,
|
|
objectNo: objectNo,
|
|
no: no,
|
|
}
|
|
await promisePost({ url: 'api/file/fileDelete', data: delParams }).then((res) => {
|
|
if (res.status === 204) {
|
|
setOriginFiles(originFiles.filter((file) => file.objectNo === objectNo && file.no !== no))
|
|
setEstimateContextState({
|
|
fileList: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no),
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
//가격표시 option 목록 최초세팅 && 주문분류 변경시
|
|
useEffect(() => {
|
|
if (estimateContextState.estimateType !== '') {
|
|
const param = {
|
|
saleStoreId: session.storeId,
|
|
sapSalesStoreCd: session.custCd,
|
|
docTpCd: estimateContextState?.estimateType,
|
|
}
|
|
|
|
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
|
|
get({ url: apiUrl }).then((res) => {
|
|
if (isNotEmptyArray(res?.data)) {
|
|
setStorePriceList(res.data)
|
|
}
|
|
})
|
|
|
|
setItemChangeYn(true)
|
|
}
|
|
}, [estimateContextState?.estimateType])
|
|
|
|
useEffect(() => {
|
|
if (estimateContextState?.priceCd) {
|
|
setShowPriceCd(estimateContextState.priceCd)
|
|
}
|
|
}, [estimateContextState?.priceCd])
|
|
|
|
//가격 표시 option 변경 이벤트
|
|
const onChangeStorePriceList = (priceCd) => {
|
|
const param = {
|
|
saleStoreId: session.storeId,
|
|
sapSalesStoreCd: session.custCd,
|
|
docTpCd: priceCd,
|
|
}
|
|
|
|
//프라이싱 했을때 priceCd setEstimateContextState
|
|
//화면에 보여지는 값은 showPriceCd로 관리
|
|
setShowPriceCd(priceCd)
|
|
|
|
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
|
|
get({ url: apiUrl }).then((res) => {
|
|
if (isNotEmptyArray(res?.data)) {
|
|
setStorePriceList(res.data)
|
|
}
|
|
})
|
|
}
|
|
|
|
//Pricing 버튼
|
|
const handlePricing = async (showPriceCd) => {
|
|
const param = {
|
|
saleStoreId: session.storeId,
|
|
sapSalesStoreCd: session.custCd,
|
|
docTpCd: estimateContextState.estimateType,
|
|
priceCd: showPriceCd,
|
|
//itemIdList: estimateContextState.itemList, //아이템 최초정보로 호출 delFlg 0인거만..
|
|
itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0'),
|
|
}
|
|
|
|
if (param.itemIdList.length > 0) {
|
|
let pass = true
|
|
|
|
param.itemIdList.map((item) => {
|
|
if (item.itemId === '') {
|
|
pass = false
|
|
}
|
|
})
|
|
|
|
if (!pass) {
|
|
//Pricing이 누락된 아이템이 있습니다. Pricing을 진행해주세요.
|
|
return alert(getMessage('estimate.detail.showPrice.pricingBtn.noItemId'))
|
|
}
|
|
}
|
|
|
|
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
|
|
let updateList = []
|
|
if (res) {
|
|
if (res.status === 200) {
|
|
const data = res.data
|
|
//기존itemList랑 프라이싱결과랑 비교해서 단가만 업뎃 서로 갯수가 안맞을 수 있음 없는 itemId면 unitPrice 0으로
|
|
//itemId로 비교해서 단가정보만 업데이트
|
|
if (data.result.code === 200) {
|
|
if (isNotEmptyArray(data.data2)) {
|
|
estimateContextState.itemList.map((item) => {
|
|
let checkYn = false
|
|
data.data2.map((item2) => {
|
|
if (item2.itemId === item.itemId) {
|
|
updateList.push({ ...item, unitPrice: item2.unitPrice })
|
|
checkYn = true
|
|
}
|
|
})
|
|
|
|
if (!checkYn) {
|
|
updateList.push({ ...item, unitPrice: '0' })
|
|
}
|
|
})
|
|
|
|
setEstimateContextState({
|
|
priceCd: showPriceCd,
|
|
itemList: updateList,
|
|
})
|
|
|
|
setItemChangeYn(true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
//row 체크박스 컨트롤
|
|
const onChangeSelect = (dispOrder) => {
|
|
const newSelection = new Set(selection)
|
|
if (newSelection.has(dispOrder)) {
|
|
newSelection.delete(dispOrder)
|
|
} else {
|
|
newSelection.add(dispOrder)
|
|
}
|
|
|
|
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 = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
|
|
if (isNaN(amount)) {
|
|
amount = 0
|
|
} else {
|
|
amount = amount.toLocaleString()
|
|
}
|
|
|
|
let updateList = []
|
|
let updates = {}
|
|
updates.amount = amount
|
|
updates.itemChangeFlg = '1'
|
|
updates.partAdd = '0'
|
|
updates.saleTotPrice = (Number(amount.replaceAll(',', '')) * estimateContextState.itemList[index].salePrice.replaceAll(',', '')).toLocaleString()
|
|
|
|
updateList = estimateContextState.itemList.map((item) => {
|
|
if (item.dispOrder === dispOrder) {
|
|
return { ...item, ...updates }
|
|
} else {
|
|
return item
|
|
}
|
|
})
|
|
|
|
setEstimateContextState({
|
|
itemList: updateList,
|
|
})
|
|
|
|
setItemChangeYn(true)
|
|
}
|
|
|
|
// 단가 변경
|
|
const onChangeSalePrice = (value, dispOrder, index) => {
|
|
//itemChangeFlg, partAdd 받아온 그대로
|
|
let salePrice = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
|
|
if (isNaN(salePrice)) {
|
|
salePrice = 0
|
|
} else {
|
|
salePrice = salePrice.toLocaleString()
|
|
}
|
|
let updateList = []
|
|
let updates = {}
|
|
updates.salePrice = salePrice
|
|
updates.saleTotPrice = (Number(salePrice.replaceAll(',', '')) * estimateContextState.itemList[index].amount.replaceAll(',', '')).toLocaleString()
|
|
|
|
updateList = estimateContextState.itemList.map((item) => {
|
|
if (item.dispOrder === dispOrder) {
|
|
return { ...item, ...updates }
|
|
} else {
|
|
return item
|
|
}
|
|
})
|
|
|
|
setEstimateContextState({
|
|
itemList: updateList,
|
|
})
|
|
|
|
setItemChangeYn(true)
|
|
}
|
|
|
|
// 아이템 자동완성 검색시 아이템 변경
|
|
const onChangeDisplayItem = (itemId, dispOrder, index) => {
|
|
const param = {
|
|
itemId: itemId,
|
|
}
|
|
const apiUrl = `/api/display-item/item-detail?${queryStringFormatter(param)}`
|
|
let updateList = []
|
|
let updates = {}
|
|
get({ url: apiUrl }).then((res) => {
|
|
// console.log('아이템상세정보:::::::', res)
|
|
updates.objectNo = objectNo
|
|
updates.planNo = planNo
|
|
updates.itemId = res.itemId
|
|
updates.itemNo = res.itemNo
|
|
updates.itemName = res.itemName
|
|
updates.itemChangeFlg = '1' //무조건 1
|
|
updates.partAdd = '1' //무조건1 NEW
|
|
updates.fileUploadFlg = res.fileUploadFlg
|
|
updates.unit = res.unit
|
|
updates.unitPrice = res.salePrice //unitPrice도 salePrice로
|
|
updates.moduleFlg = res.moduleFlg
|
|
updates.pkgMaterialFlg = res.pkgMaterialFlg
|
|
updates.pnowW = res.pnowW
|
|
updates.salePrice = res.salePrice
|
|
updates.specification = res.specification
|
|
updates.unit = res.unit
|
|
updates.specialNoteCd = res.spnAttrCds
|
|
updates.itemGroup = res.itemGroup
|
|
updates.delFlg = '0' // 삭제플래그 0
|
|
// updates.saleTotPrice = res.salePrice * estimateContextState.itemList[index].amount
|
|
updates.saleTotPrice = '0' //추가때는 수량을 안받아서 합계를 무조건 0으로
|
|
//104671
|
|
let bomList = res.itemBomList
|
|
|
|
updateList = estimateContextState.itemList.map((item) => {
|
|
if (item.dispOrder === dispOrder) {
|
|
return { ...item, ...updates }
|
|
} else if (item.paDispOrder === dispOrder) {
|
|
return { ...item, delFlg: '1' }
|
|
} else {
|
|
return item
|
|
}
|
|
})
|
|
//paDispOrder
|
|
if (bomList) {
|
|
bomList.map((bomItem, index) => {
|
|
let newItemDispOrder = Math.max(...estimateContextState.itemList.map((item) => item.dispOrder))
|
|
bomItem.dispOrder = index + 1 + newItemDispOrder
|
|
bomItem.delFlg = '0'
|
|
bomItem.objectNo = objectNo
|
|
bomItem.planNo = planNo
|
|
})
|
|
|
|
setEstimateContextState({
|
|
itemList: [...updateList, ...bomList],
|
|
})
|
|
} else {
|
|
setEstimateContextState({
|
|
itemList: updateList,
|
|
})
|
|
}
|
|
|
|
setItemChangeYn(true)
|
|
})
|
|
}
|
|
|
|
//제품 삭제
|
|
const removeItem = () => {
|
|
const array = [...selection]
|
|
let delList = []
|
|
estimateContextState.itemList.filter((row) => {
|
|
array.map((row2) => {
|
|
if (row2 === row.dispOrder) {
|
|
delList.push({ ...row })
|
|
}
|
|
if (row2 === row.paDispOrder) {
|
|
delList.push({ ...row })
|
|
}
|
|
})
|
|
})
|
|
|
|
const updateList = estimateContextState.itemList.map((item) => {
|
|
const isDeleted = delList.some((row) => item.delFlg === '1' || item.dispOrder === row.dispOrder)
|
|
return {
|
|
...item,
|
|
delFlg: isDeleted ? '1' : '0',
|
|
}
|
|
})
|
|
|
|
let delCnt = 0
|
|
updateList.map((item) => {
|
|
if (item.delFlg === '1') {
|
|
delCnt++
|
|
}
|
|
})
|
|
|
|
if (delCnt === updateList.length) {
|
|
return alert(getMessage('estimate.detail.save.requiredItem'))
|
|
}
|
|
|
|
setEstimateContextState({
|
|
itemList: updateList,
|
|
})
|
|
|
|
setSelection(new Set())
|
|
setItemChangeYn(true)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (itemChangeYn) {
|
|
let totAmount = 0
|
|
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(',', ''))
|
|
|
|
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, 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">
|
|
<div className="sub-content-inner">
|
|
{/* 물건번호, 견적서번호, 등록일, 변경일시 시작 */}
|
|
<div className="sub-content-box">
|
|
<div className="sub-table-box">
|
|
<div className="estimate-list-wrap one">
|
|
<div className="estimate-box">
|
|
<div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div>
|
|
<div className="estimate-name">
|
|
{objectNo} (Plan No: {planNo})
|
|
</div>
|
|
</div>
|
|
<div className="estimate-box">
|
|
<div className="estimate-tit">{getMessage('estimate.detail.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">
|
|
{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">
|
|
{estimateContextState?.lastEditDatetime ? `${dayjs(estimateContextState.lastEditDatetime).format('YYYY.MM.DD HH:mm')}` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* 물건번호, 견적서번호, 등록일, 변경일시 끝 */}
|
|
{/* 기본정보 시작 */}
|
|
<div className="sub-content-box">
|
|
<div className="sub-table-box">
|
|
<div className="table-box-title-wrap">
|
|
<div className="title-wrap">
|
|
<h3>{getMessage('estimate.detail.header.title')}</h3>
|
|
</div>
|
|
</div>
|
|
<div className="common-table bt-able">
|
|
<table>
|
|
<colgroup>
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>
|
|
<tr>
|
|
{/* 1차 판매점명 */}
|
|
<th>{getMessage('estimate.detail.saleStoreId')}</th>
|
|
<td>{estimateContextState?.firstSaleStoreName}</td>
|
|
{/* 견적일 */}
|
|
<th>
|
|
{getMessage('estimate.detail.estimateDate')} <span className="important">*</span>
|
|
</th>
|
|
<td>
|
|
<div className="date-picker" style={{ width: '350px' }}>
|
|
<SingleDatePicker {...singleDatePickerProps} />
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
{/* 2차 판매점명 */}
|
|
<th>{getMessage('estimate.detail.otherSaleStoreId')}</th>
|
|
<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={estimateContextState?.charger} onBlur={handleBlurCharger} />
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
{/* 안건명 */}
|
|
<th>
|
|
{getMessage('estimate.detail.objectName')} <span className="important">*</span>
|
|
</th>
|
|
<td colSpan={3}>
|
|
<div className="form-flex-wrap">
|
|
<div className="input-wrap mr5" style={{ width: '610px' }}>
|
|
<input type="text" className="input-light" defaultValue={estimateContextState?.objectName} onBlur={handleBlurObjectName} />
|
|
</div>
|
|
<div className="select-wrap" style={{ width: '200px' }}>
|
|
<Select
|
|
id="objectNameOmit"
|
|
instanceId="objectNameOmit"
|
|
className="react-select-custom"
|
|
classNamePrefix="custom"
|
|
placeholder="Select"
|
|
options={honorificCodeList}
|
|
onChange={(e) => {
|
|
if (isObjectNotEmpty(e)) {
|
|
setEstimateContextState({ objectNameOmit: e.clCodeNm })
|
|
} else {
|
|
setEstimateContextState({ objectNameOmit: '' })
|
|
}
|
|
}}
|
|
getOptionLabel={(x) => x.clCodeNm}
|
|
getOptionValue={(x) => x.clCode}
|
|
isClearable={false}
|
|
isSearchable={false}
|
|
value={honorificCodeList.filter(function (option) {
|
|
return option.clCodeNm === estimateContextState.objectNameOmit
|
|
})}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
{/* 물건정보에서 입력한 메모 */}
|
|
<th>{getMessage('estimate.detail.objectRemarks')}</th>
|
|
<td colSpan={3}>{estimateContextState?.objectRemarks}</td>
|
|
</tr>
|
|
<tr>
|
|
{/* 주문분류 */}
|
|
<th>
|
|
{getMessage('estimate.detail.estimateType')} <span className="important">*</span>
|
|
</th>
|
|
<td colSpan={3}>
|
|
<div className="radio-wrap">
|
|
<div className="d-check-radio light mr10">
|
|
<input
|
|
type="radio"
|
|
name="estimateType"
|
|
id="YJSS"
|
|
value={'YJSS'}
|
|
checked={estimateContextState?.estimateType === 'YJSS' ? true : false}
|
|
onChange={(e) => {
|
|
//주문분류
|
|
setEstimateContextState({ estimateType: e.target.value })
|
|
}}
|
|
/>
|
|
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
|
|
</div>
|
|
<div className="d-check-radio light">
|
|
<input
|
|
type="radio"
|
|
name="estimateType"
|
|
id="YJOD"
|
|
value={'YJOD'}
|
|
checked={estimateContextState?.estimateType === 'YJOD' ? true : false}
|
|
onChange={(e) => {
|
|
setEstimateContextState({ estimateType: e.target.value })
|
|
}}
|
|
/>
|
|
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
{/* 지붕재・사양시공 최대4개*/}
|
|
<th>{getMessage('estimate.detail.roofCns')}</th>
|
|
<td colSpan={3}>
|
|
{estimateContextState?.roofMaterialIdMulti?.split('、').map((row, index) => {
|
|
//지붕재
|
|
let roofList = row
|
|
let roofListLength = estimateContextState?.roofMaterialIdMulti?.split('、').length
|
|
let style = 'mb5'
|
|
if (roofListLength == index + 1) {
|
|
style = ''
|
|
}
|
|
//사양시공
|
|
let constructSpecificationMulti = estimateContextState?.constructSpecificationMulti?.split('、')
|
|
|
|
return (
|
|
<>
|
|
<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>
|
|
<div className="input-wrap" style={{ width: '200px' }}>
|
|
<input type="text" className="input-light" value={constructSpecificationMulti[index]} readOnly />
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
})}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
{/* 비고 */}
|
|
<th>{getMessage('estimate.detail.remarks')}</th>
|
|
<td colSpan={3}>
|
|
<div className="input-wrap">
|
|
<input type="text" className="input-light" defaultValue={estimateContextState?.remarks} onBlur={handleBlurRemarks} />
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/* 파일첨부 시작 */}
|
|
<div className="table-box-title-wrap">
|
|
<div className="title-wrap">
|
|
<h3>{getMessage('estimate.detail.header.fileList1')}</h3>
|
|
<div className="d-check-box light mr5">
|
|
<input
|
|
type="checkbox"
|
|
id="next"
|
|
checked={estimateContextState?.fileFlg === '0' ? false : true}
|
|
onChange={(e) => {
|
|
setEstimateContextState({
|
|
fileFlg: e.target.checked ? '1' : '0',
|
|
})
|
|
}}
|
|
/>
|
|
<label htmlFor="next" style={{ color: '#101010' }}>
|
|
{getMessage('estimate.detail.fileFlg')}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="common-table mb10">
|
|
<table>
|
|
<colgroup>
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>
|
|
<tr>
|
|
<th>{getMessage('estimate.detail.header.fileList1')}</th>
|
|
<td>
|
|
<EstimateFileUploader {...fileUploadProps} />
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/* 첨부파일 목록 시작 */}
|
|
<div className="common-table bt-able">
|
|
<table>
|
|
<colgroup>
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>
|
|
<tr>
|
|
<th>{getMessage('estimate.detail.header.fileList2')}</th>
|
|
<td>
|
|
<div className="drag-file-box">
|
|
<ul className="file-list">
|
|
{originFiles.length > 0 &&
|
|
originFiles.map((originFile) => {
|
|
return (
|
|
<li className="file-item" key={uuidv4()}>
|
|
<span onClick={() => handleEstimateFileDownload(originFile)}>
|
|
{originFile.faileName}
|
|
<button
|
|
type="button"
|
|
className="delete"
|
|
onClick={(e) => {
|
|
deleteOriginFile(originFile.objectNo, originFile.no)
|
|
e.stopPropagation()
|
|
}}
|
|
></button>
|
|
</span>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/* 첨부파일 목록 끝 */}
|
|
{/* 파일첨부 끝 */}
|
|
{/* 견적특이사항 시작 */}
|
|
<div className="table-box-title-wrap">
|
|
<div className="title-wrap">
|
|
<h3 className="product">{getMessage('estimate.detail.header.specialEstimate')}</h3>
|
|
<div className="product_tit">{getMessage('estimate.detail.header.specialEstimateProductInfo')}</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 className={`estimate-check-wrap ${hidden ? 'hide' : ''}`}>
|
|
<div className="estimate-check-inner">
|
|
<div className="special-note-check-wrap">
|
|
{/* SpecialNoteList반복문 */}
|
|
{specialNoteList.length > 0 &&
|
|
specialNoteList.map((row) => {
|
|
return (
|
|
<div
|
|
key={uuidv4()}
|
|
className="special-note-check-item"
|
|
onClick={(event) => {
|
|
settingShowContent(row.code, event)
|
|
}}
|
|
>
|
|
<div className="d-check-box light">
|
|
<input
|
|
type="checkbox"
|
|
id={row.code}
|
|
checked={!!row.text}
|
|
disabled={row.code === 'ATTR001' ? true : false}
|
|
onChange={(event) => {
|
|
setSpecialNoteList((specialNote) =>
|
|
specialNote.map((temp) => (temp.code === row.code ? { ...temp, text: !temp.text } : temp)),
|
|
)
|
|
settingShowContent(row.code, event)
|
|
}}
|
|
/>
|
|
<label htmlFor={row.code}>{row.codeNm}</label>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
{/* 견적특이사항 선택한 내용 영역시작 */}
|
|
<div className="calculation-estimate">
|
|
{specialNoteList.map((row) => {
|
|
if (row.code === showContentCode) {
|
|
return (
|
|
<dl key={uuidv4()}>
|
|
<dt>{row.codeNm}</dt>
|
|
<dd dangerouslySetInnerHTML={{ __html: row.remarks }}></dd>
|
|
</dl>
|
|
)
|
|
}
|
|
})}
|
|
</div>
|
|
{/* 견적특이사항 선택한 내용 영역끝 */}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 견적 특이사항 코드영역 끝 */}
|
|
|
|
{/* 견적특이사항 영역끝 */}
|
|
{/* 제품정보 시작 */}
|
|
<div className="table-box-title-wrap">
|
|
<div className="title-wrap">
|
|
<h3>{getMessage('estimate.detail.header.specialEstimateProductInfo')}</h3>
|
|
</div>
|
|
</div>
|
|
<div className="esimate-wrap">
|
|
<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(estimateContextState?.totAmount)}</div>
|
|
</div>
|
|
<div className="estimate-box">
|
|
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.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(estimateContextState?.supplyPrice)}</div>
|
|
</div>
|
|
<div className="estimate-box">
|
|
<div className="estimate-tit">{getMessage('estimate.detail.sepcialEstimateProductInfo.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(estimateContextState?.totPrice)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* YJOD면 아래영역 숨김 */}
|
|
<div className="common-table bt-able" style={{ display: estimateContextState?.estimateType === 'YJSS' ? '' : 'none' }}>
|
|
<table>
|
|
<colgroup>
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
<col style={{ width: '160px' }} />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>
|
|
<tr>
|
|
<th>
|
|
{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgUnitPrice')}
|
|
<span className="important">*</span>
|
|
</th>
|
|
<td>
|
|
<div className="input-wrap">
|
|
<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>{convertNumberToPriceDecimal(estimateContextState?.totVolKw)}</td>
|
|
<th>{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgPrice')}</th>
|
|
<td>{convertNumberToPriceDecimal(estimateContextState?.pkgTotPrice)}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/* 제품정보 끝 */}
|
|
{/* 가격표시영역시작 */}
|
|
<div className="estimate-product-option">
|
|
<div className="product-price-wrap">
|
|
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
|
|
<div className="select-wrap">
|
|
{session?.storeLvl === '1' ? (
|
|
<select
|
|
key={uuidv4()}
|
|
className="select-light"
|
|
onChange={(e) => {
|
|
onChangeStorePriceList(e.target.value)
|
|
}}
|
|
value={showPriceCd}
|
|
>
|
|
{storePriceList.length > 0 && storePriceList.map((row) => <option value={row.priceCd}>{row.priceNm}</option>)}
|
|
</select>
|
|
) : (
|
|
<select key={uuidv4()} className="select-light">
|
|
<option value="UNIT_PRICE">{getMessage('estimate.detail.header.unitPrice')}</option>
|
|
</select>
|
|
)}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="btn-origin grey ml5"
|
|
onClick={() => {
|
|
handlePricing(showPriceCd)
|
|
}}
|
|
>
|
|
{getMessage('estimate.detail.showPrice.pricingBtn')}
|
|
</button>
|
|
</div>
|
|
<div className="product-edit-wrap">
|
|
<ul className="product-edit-explane">
|
|
<li className="explane-item item01">
|
|
<span className="ico"></span>
|
|
{getMessage('estimate.detail.showPrice.description1')}
|
|
</li>
|
|
<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">
|
|
{/* <button className="btn-origin navy mr5" type="button" onClick={addItem}> */}
|
|
<button
|
|
className="btn-origin navy mr5"
|
|
type="button"
|
|
onClick={() => {
|
|
addItem()
|
|
// setItemChangeYn(true)
|
|
}}
|
|
>
|
|
<span className="plus"></span>
|
|
{getMessage('estimate.detail.showPrice.addItem')}
|
|
</button>
|
|
<button className="btn-origin grey" type="button" onClick={removeItem}>
|
|
<span className="minus"></span>
|
|
{getMessage('estimate.detail.showPrice.delItem')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* 가격표시영역끝 */}
|
|
{/* html테이블시작 */}
|
|
<div className="esimate-table">
|
|
<table>
|
|
<colgroup>
|
|
<col width={50} />
|
|
<col width={100} />
|
|
<col />
|
|
<col width={200} />
|
|
<col width={100} />
|
|
<col width={100} />
|
|
<col width={200} />
|
|
<col width={240} />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<div className="d-check-box pop no-text" style={{ display: 'none' }}>
|
|
<input type="checkbox" id="ch97" />
|
|
<label htmlFor="ch97"></label>
|
|
</div>
|
|
</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.dispOrder')}</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.itemId')}</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.itemNo')}</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.amount')}</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.unit')}</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.salePrice')}</th>
|
|
<th>{getMessage('estimate.detail.itemTableHeader.saleTotPrice')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{estimateContextState?.itemList.length > 0 &&
|
|
estimateContextState.itemList.map((item, index) => {
|
|
if (item.delFlg === '0') {
|
|
return (
|
|
<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
|
|
})}
|
|
/>
|
|
</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>
|
|
</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={convertNumberToPriceDecimal(item?.salePrice.replaceAll(',', ''))}
|
|
disabled={
|
|
estimateContextState?.estimateType === 'YJSS'
|
|
? item?.paDispOrder
|
|
? true
|
|
: item.pkgMaterialFlg !== '1'
|
|
: item.itemId === '' || !!item?.paDispOrder
|
|
}
|
|
onChange={(e) => {
|
|
onChangeSalePrice(e.target.value, item.dispOrder, index)
|
|
}}
|
|
/>
|
|
</div>
|
|
{/* <div className="btn-area">
|
|
<span className="tb_ico open_check">OPEN아이콘 처리</span>
|
|
</div> */}
|
|
</div>
|
|
</td>
|
|
<td className="al-r">{convertNumberToPriceDecimal(item?.saleTotPrice.replaceAll(',', ''))}</td>
|
|
</tr>
|
|
)
|
|
} else {
|
|
return null
|
|
}
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* html테이블끝 */}
|
|
</div>
|
|
</div>
|
|
{/* 기본정보끝 */}
|
|
</div>
|
|
{productFeaturesPopupOpen && (
|
|
<ProductFeaturesPop
|
|
specialNoteList={specialNoteList}
|
|
showProductFeatureData={showProductFeatureData}
|
|
setProductFeaturesPopupOpen={setProductFeaturesPopupOpen}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|