import { useAxios } from '@/hooks/useAxios' import { useContext, useEffect, useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' import { globalLocaleStore } from '@/store/localeAtom' import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom' import { isObjectNotEmpty, isEmptyArray } from '@/util/common-utils' import { SessionContext } from '@/app/SessionProvider' import { useMessage } from '@/hooks/useMessage' import { useRouter, useSearchParams } from 'next/navigation' import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' import { QcastContext } from '@/app/QcastProvider' import { useSwal } from '@/hooks/useSwal' // Constants const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의 // Helper functions const updateItemInList = (itemList, dispOrder, updates) => { return itemList.map((item) => (item.dispOrder === dispOrder ? { ...item, ...updates } : item)) } export const useEstimateController = (planNo, flag) => { const { swalFire } = useSwal() const [fileList, setFileList] = useState([]) const { setIsGlobalLoading } = useContext(QcastContext) const router = useRouter() const { session } = useContext(SessionContext) const globalLocaleState = useRecoilValue(globalLocaleStore) const objectRecoil = useRecoilValue(floorPlanObjectState) const [estimateData, setEstimateData] = useRecoilState(estimateState) const { getMessage } = useMessage() const { promiseGet, post, promisePost } = useAxios(globalLocaleState) const [isLoading, setIsLoading] = useState(false) const { estimateContextState, setEstimateContextState } = useContext(FloorPlanContext) const searchParams = useSearchParams() useEffect(() => { if (planNo && !isLoading) { //견적서 화면에서 새로고침시 리코일 사라지는 현상 대응 let recoilObjectNo = objectRecoil.floorPlanObjectNo ? objectRecoil.floorPlanObjectNo : searchParams.get('objectNo') if (recoilObjectNo && planNo) { if (!flag) { fetchSetting(recoilObjectNo, planNo) } } } }, []) useEffect(() => { if (fileList.length > 0) { realSave(fileList) } }, [fileList]) // 상세 조회 const fetchSetting = async (objectNo, planNo, resetFlag) => { try { await promiseGet({ url: `/api/estimate/${objectNo}/${planNo}/detail` }).then((res) => { setIsGlobalLoading(true) if (res.status === 200) { if (isObjectNotEmpty(res.data)) { if (resetFlag) { res.data.originFiles = [] res.data.originFile = [] res.data.resetFlag = 'Y' } else { res.data.resetFlag = 'N' } if (res.data.itemList.length > 0) { res.data.itemList.map((item) => { item.delFlg = '0' }) } if (res.data.pkgAsp === null || res.data.pkgAsp == undefined) { res.data.pkgAsp = '0.00' } else { const number = parseFloat(res.data.pkgAsp) const roundedNumber = isNaN(number) ? '0.00' : number.toFixed(2) res.data.pkgAsp = roundedNumber.toString() } setEstimateContextState(res.data) } else { swalFire({ text: getMessage('stuff.detail.header.notExistObjectNo'), type: 'alert', icon: 'error' }) setIsLoading(true) setIsGlobalLoading(false) } } }) setIsLoading(true) setIsGlobalLoading(false) } catch (error) { console.error('견적서 상세조회 Error: ', error) swalFire({ text: getMessage('stuff.detail.header.notExistObjectNo'), type: 'alert', icon: 'error' }) setIsLoading(true) setIsGlobalLoading(false) } } const updateItem = (dispOrder, updates) => { setEstimateContextState({ itemList: updateItemInList(estimateContextState.itemList, dispOrder, updates), }) } const addItem = () => { let newItemDispOrder = estimateContextState.itemList.length === 0 ? 0 : Math.max(...estimateContextState.itemList.map((item) => item.dispOrder)) newItemDispOrder = (Math.floor(newItemDispOrder / 100) + 1) * 100 setEstimateContextState({ itemList: [ ...estimateContextState.itemList, { objectNo: objectRecoil.floorPlanObjectNo, planNo: planNo, dispOrder: newItemDispOrder.toString(), itemId: '', //제품번호 itemNo: '', itemName: '', //형명 amount: '', //수량 unitPrice: '0', unit: '', //단위 salePrice: '', //단가 saleTotPrice: '', //금액(부가세별도) itemChangeFlg: '1', //추가시 체인지플래그 1로 partAdd: '1', //NEW 체인지 플래그 delFlg: '0', //삭제 플래그 0 삭제하면 1 addFlg: true, paDispOrder: null, dispCableFlg: '0', }, ], }) } useEffect(() => { setEstimateData({ ...estimateContextState, userId: session.userId, sapSalesStoreCd: session.custCd }) }, [estimateContextState]) // 첨부파일 다운로드 const handleEstimateFileDownload = async (originFile) => { const options = { responseType: 'blob' } await promisePost({ url: `/api/file/fileDownload`, data: originFile, option: options }) .then((resultData) => { if (resultData) { const blob = new Blob([resultData.data], { type: resultData.headers['content-type'] || 'application/octet-stream' }) const fileUrl = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = fileUrl link.download = originFile.faileName document.body.appendChild(link) link.click() link.remove() window.URL.revokeObjectURL(fileUrl) } }) .catch((error) => { console.log('::FileDownLoad Error::', error) return swalFire({ text: getMessage('File does not exist'), type: 'alert' }) }) } //견적서 저장 const handleEstimateSubmit = async () => { //0. 필수체크 let flag = true let originFileFlg = false let fileFlg = true let itemFlg = true if (estimateData?.charger === null || estimateData?.charger?.trim().length === 0) { flag = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredCharger'), type: 'alert', icon: 'warning' }) } if (estimateData?.objectName === null || estimateData?.objectName?.trim().length === 0) { flag = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredObjectName'), type: 'alert', icon: 'warning' }) } if (isNaN(Date.parse(estimateData.estimateDate))) { flag = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredEstimateDate'), type: 'alert', icon: 'warning' }) } if (estimateData.estimateType === 'YJSS') { let pkgAsp = estimateData.pkgAsp if (pkgAsp === '0') { flag = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredPkgAsp'), type: 'alert', icon: 'warning' }) } } //기존에 첨부된 파일이 있으면 파일첨부관련 통과 if (estimateData?.originFiles?.length > 0) { let cnt = estimateData.originFiles.filter((file) => file.delFlg === '0').length if (cnt == 0) { originFileFlg = false } else { originFileFlg = true } } if (flag) { if (!originFileFlg) { //기존에 첨부된 파일이 없으면 if (isEmptyArray(estimateData.newFileList)) { //새로 첨부한 파일이 없으면 //북면 먼저 체크 if (estimateData.fileFlg === '0') { if (estimateData?.northArrangement === '1') { fileFlg = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredNorthArrangementFileUpload'), type: 'alert', icon: 'warning' }) } } if (estimateData.itemList.length > 1) { estimateData.itemList.map((row) => { if (row.delFlg === '0') { if (row.fileUploadFlg === '1') { if (fileFlg) { if (estimateData.fileFlg === '0') { fileFlg = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredFileUpload'), type: 'alert', icon: 'warning' }) } } } } }) } } } } if (fileFlg) { estimateData.itemList.map((item) => { if (item.delFlg === '0') { if (item.addFlg) { if (item.itemId === '') { itemFlg = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredItemId'), type: 'alert', icon: 'warning' }) } } item.amount = item.amount?.replaceAll(',', '') item.salePrice = Number(item.salePrice?.replaceAll(',', '')).toFixed(2) item.saleTotPrice = Number(item.saleTotPrice?.replaceAll(',', '')).toFixed(2) if (!item.paDispOrder) { if (itemFlg) { if (isNaN(item.amount)) { item.amount = '0' } if (item.amount < 1) { itemFlg = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredAmount'), type: 'alert', icon: 'warning' }) } if (estimateData.estimateType !== 'YJSS') { if (isNaN(item.salePrice)) { item.salePrice = '0' } if (item.openFlg !== '1') { if (item.salePrice < 1) { itemFlg = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredSalePrice'), type: 'alert', icon: 'warning' }) } } estimateData.pkgAsp = '0' estimateData.pkgTotPrice = '0' } else { if (item.pkgMaterialFlg === '1') { if (isNaN(item.salePrice)) { itemFlg = false setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredSalePrice'), type: 'alert', icon: 'warning' }) } } } } } else { //견적서를 복사하면 API에서 봄컴포넌트는 salePrice, unitPrice를 0으로 내려옴..front에서 처리 if (item.salePrice === null) { item.salePrice = '0' } else if (isNaN(item.salePrice)) { item.salePrice = '0' } if (item.unitPrice === null) { item.unitPrice = '0' } else if (isNaN(item.unitPrice)) { item.unitPrice = '0' } //봄 컴포넌트 제품은 0으로 item.openFlg = '0' } } }) estimateData.itemList = estimateData.itemList.filter((item) => item.delFlg === '0' || !item.addFlg) let delCnt = 0 estimateData.itemList.map((item) => { if (item.delFlg === '1') { delCnt++ } }) if (delCnt === estimateData.itemList.length) { setIsGlobalLoading(false) return swalFire({ text: getMessage('estimate.detail.save.requiredItem'), type: 'alert', icon: 'warning' }) } } if (flag && fileFlg && itemFlg) { //1. 첨부파일 저장시작 const formData = new FormData() if (estimateData?.newFileList?.length > 0) { estimateData.newFileList.forEach((file) => { formData.append('files', file) }) formData.append('objectNo', estimateData.objectNo) formData.append('planNo', estimateData.planNo) formData.append('category', '10') formData.append('userId', estimateData.userId) await post({ url: '/api/file/fileUpload', data: formData }).then((res) => { setFileList(res) setIsGlobalLoading(false) }) } else { setFileList([]) realSave() } } } const realSave = async (fileList) => { //첨부파일저장끝 let option = [] estimateData.itemList.forEach((item) => { if (item.specialNoteCd) { let split2 = item.specialNoteCd.split('、') option = option.concat(split2) } }) let estimateOptions = '' let estimateOptionsArray estimateData.specialNoteList.map((item) => { if (item.pkgYn === '0') { if (item.check) { if (estimateOptions === '') { estimateOptions = item.code } else { estimateOptions += '、' + item.code } } } else { if (item.check) { let flg = '0' for (let i = 0; i < estimateData.uniqueData.length; i++) { if (item.code.indexOf(estimateData.uniqueData[i]) > -1) { flg = '1' } if (flg === '1') { estimateOptions += '、' + estimateData.uniqueData[i] } } } } }) estimateOptionsArray = estimateOptions.split('、').sort() estimateOptionsArray = Array.from(new Set(estimateOptionsArray)) estimateOptions = estimateOptionsArray.join('、') estimateData.estimateOption = estimateOptions // console.log('최종아이템:::', estimateData.itemList) if (fileList?.length > 0) { estimateData.fileList = fileList } else { estimateData.fileList = [] } if (estimateData.originFiles?.length > 0) { estimateData.deleteFileList = estimateData.originFiles?.filter((item) => item.delFlg === '1') } else { estimateData.deleteFileList = [] } if (estimateData.estimateType === 'YJSS') { estimateData.pkgAsp = estimateData.pkgAsp.replaceAll(',', '') } //2. 상세데이터 저장 try { setIsGlobalLoading(true) await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => { if (res.status === 201) { estimateData.newFileList = [] swalFire({ text: getMessage('estimate.detail.save.alertMsg'), type: 'alert' }) fetchSetting(estimateData.objectNo, estimateData.planNo) } }) } catch (e) { setIsGlobalLoading(false) console.error('error::::::::::::', e.response.data.message) } } /** * 견적서 복사버튼 * (견적서 번호(estimateData.docNo)가 생성된 이후 버튼 활성화 ) * T01관리자 계정 및 1차판매점에게만 제공 */ const handleEstimateCopy = async (sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId, setEstimateCopyPopupOpen) => { if (saleStoreId === '') { return swalFire({ text: getMessage('estimate.detail.productFeaturesPopup.requiredStoreId'), type: 'alert', icon: 'warning', }) } if (copyReceiveUser.trim().length === 0) { return swalFire({ text: getMessage('estimate.detail.productFeaturesPopup.requiredReceiveUser'), type: 'alert', icon: 'warning', }) } else { if (checkLength(copyReceiveUser.trim()) > 10) { return swalFire({ text: getMessage('stuff.detail.tempSave.message2'), type: 'alert', icon: 'warning' }) } } const params = { saleStoreId: session.storeId, sapSalesStoreCd: session.custCd, objectNo: estimateData.objectNo, planNo: sendPlanNo, copySaleStoreId: otherSaleStoreId ? otherSaleStoreId : saleStoreId, copyReceiveUser: copyReceiveUser, userId: session.userId, } setIsGlobalLoading(true) await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }).then((res) => { setIsGlobalLoading(false) if (res.status === 201) { if (isObjectNotEmpty(res.data)) { let newObjectNo = res.data.objectNo swalFire({ text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'), type: 'alert', confirmFn: () => { setEstimateCopyPopupOpen(false) //팝업닫고 router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false }) }, }) } } else { setIsGlobalLoading(false) } }) } const checkLength = (value) => { let str = new String(value) let _byte = 0 if (str.length !== 0) { for (let i = 0; i < str.length; i++) { let str2 = str.charAt(i) if (encodeURIComponent(str2).length > 4) { _byte += 2 } else { _byte++ } } } return _byte } return { estimateContextState, setEstimateContextState, updateItem, addItem, handleEstimateSubmit, fetchSetting, handleEstimateFileDownload, handleEstimateCopy, } }