견적서 상세

This commit is contained in:
basssy 2024-11-07 17:57:54 +09:00
parent ed8a981631
commit 6f0f0883f8
4 changed files with 249 additions and 75 deletions

View File

@ -9,12 +9,13 @@ import SingleDatePicker from '../common/datepicker/SingleDatePicker'
import EstimateFileUploader from './EstimateFileUploader' import EstimateFileUploader from './EstimateFileUploader'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom' import { globalLocaleStore } from '@/store/localeAtom'
import { isNotEmptyArray, isObjectNotEmpty } from '@/util/common-utils' import { isNotEmptyArray, isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useCommonCode } from '@/hooks/common/useCommonCode' import { useCommonCode } from '@/hooks/common/useCommonCode'
import Select from 'react-select'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
import Select, { components } from 'react-select'
// import EstimateItemTable from './EstimateItemTable'
export default function Estimate({ params }) { export default function Estimate({ params }) {
const { session } = useContext(SessionContext) const { session } = useContext(SessionContext)
@ -33,6 +34,8 @@ export default function Estimate({ params }) {
const { findCommonCode } = useCommonCode() const { findCommonCode } = useCommonCode()
const [honorificCodeList, setHonorificCodeList] = useState([]) // const [honorificCodeList, setHonorificCodeList] = useState([]) //
const [storePriceList, setStorePriceList] = useState([]) // option
const [startDate, setStartDate] = useState(new Date()) const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = { const singleDatePickerProps = {
startDate, startDate,
@ -44,7 +47,7 @@ export default function Estimate({ params }) {
// //
const { state, setState } = useEstimateController(params.pid) const { state, setState } = useEstimateController(params.pid)
// LIST const [itemList, setItemList] = useState([])
// List // List
const [specialNoteList, setSpecialNoteList] = useState([]) const [specialNoteList, setSpecialNoteList] = useState([])
@ -155,6 +158,46 @@ export default function Estimate({ params }) {
}) })
} }
//
useEffect(() => {
if (isNotEmptyArray(state.itemList)) {
setItemList(state.itemList)
}
}, [state?.itemList])
// option
useEffect(() => {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
docTpCd: state?.estimateType,
}
const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}`
get({ url: apiUrl }).then((res) => {
if (isNotEmptyArray(res?.data)) {
setStorePriceList(res.data)
}
})
}, [state?.estimateType])
//Pricing
const handlePricing = async (priceCd) => {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
docTpCd: state.estimateType,
priceCd: priceCd,
itemIdList: [], //
}
console.log('param::', param)
return
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
console.log('프라이싱결과::::::', res)
// ............SUCK!!!
})
}
return ( return (
<div className="sub-content estimate"> <div className="sub-content estimate">
<div className="sub-content-inner"> <div className="sub-content-inner">
@ -304,6 +347,7 @@ export default function Estimate({ params }) {
value={'YJSS'} value={'YJSS'}
checked={state?.estimateType === 'YJSS' ? true : false} checked={state?.estimateType === 'YJSS' ? true : false}
onChange={(e) => { onChange={(e) => {
//
setState({ estimateType: e.target.value }) setState({ estimateType: e.target.value })
}} }}
/> />
@ -380,9 +424,18 @@ export default function Estimate({ params }) {
<div className="title-wrap"> <div className="title-wrap">
<h3>{getMessage('estimate.detail.header.fileList1')}</h3> <h3>{getMessage('estimate.detail.header.fileList1')}</h3>
<div className="d-check-box light mr5"> <div className="d-check-box light mr5">
<input type="checkbox" id="next" /> <input
type="checkbox"
id="next"
checked={state?.fileFlg === '0' ? false : true}
onChange={(e) => {
setState({
fileFlg: e.target.checked ? '1' : '0',
})
}}
/>
<label htmlFor="next" style={{ color: '#101010' }}> <label htmlFor="next" style={{ color: '#101010' }}>
{getMessage('estimate.detail.nextSubmit')} {getMessage('estimate.detail.fileFlg')}
</label> </label>
</div> </div>
</div> </div>
@ -416,7 +469,7 @@ export default function Estimate({ params }) {
<td> <td>
<div className="drag-file-box"> <div className="drag-file-box">
<ul className="file-list"> <ul className="file-list">
{isNotEmptyArray(originFiles) && {originFiles.length > 0 &&
originFiles.map((originFile) => { originFiles.map((originFile) => {
return ( return (
<li className="file-item"> <li className="file-item">
@ -492,7 +545,7 @@ export default function Estimate({ params }) {
} }
})} })}
</div> </div>
{/* 견적특이사항 선택한 내용?영역끝 */} {/* 견적특이사항 선택한 내용 영역끝 */}
</div> </div>
</div> </div>
@ -565,12 +618,30 @@ export default function Estimate({ params }) {
<div className="product-price-wrap"> <div className="product-price-wrap">
<div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div> <div className="product-price-tit">{getMessage('estimate.detail.header.showPrice')}</div>
<div className="select-wrap"> <div className="select-wrap">
<select className="select-light" name="" id=""> {session?.storeLvl === '1' ? (
<option value="">111</option> <select
<option value="">222</option> className="select-light"
</select> onChange={(e) => {
setState({ priceCd: e.target.value })
}}
>
{storePriceList.length > 0 && storePriceList.map((row) => <option value={row.priceCd}>{row.priceNm}</option>)}
</select>
) : (
<select className="select-light">
<option value="UNIT_PRICE">{getMessage('estimate.detail.header.unitPrice')}</option>
</select>
)}
</div> </div>
<button className="btn-origin grey ml5">{getMessage('estimate.detail.showPrice.btn1')}</button> <button
className="btn-origin grey ml5"
onClick={() => {
// console.log('priceCd::', state.priceCd)
handlePricing(state.priceCd)
}}
>
{getMessage('estimate.detail.showPrice.pricingBtn')}
</button>
</div> </div>
<div className="product-edit-wrap"> <div className="product-edit-wrap">
<ul className="product-edit-explane"> <ul className="product-edit-explane">
@ -605,7 +676,83 @@ export default function Estimate({ params }) {
</div> </div>
{/* 가격표시영역끝 */} {/* 가격표시영역끝 */}
{/* html테이블시작 */} {/* html테이블시작 */}
<div className="q-grid no-cols"></div> <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.col1')}</th>
<th>{getMessage('estimate.detail.itemTableHeader.col2')}</th>
<th>{getMessage('estimate.detail.itemTableHeader.col3')}</th>
<th>{getMessage('estimate.detail.itemTableHeader.col4')}</th>
<th>{getMessage('estimate.detail.itemTableHeader.col5')}</th>
<th>{getMessage('estimate.detail.itemTableHeader.col6')}</th>
<th>{getMessage('estimate.detail.itemTableHeader.col7')}</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">
<div className="d-check-box light no-text">
<input type="checkbox" id="ch98" />
<label htmlFor="ch98"></label>
</div>
</td>
<td className="al-r">100</td>
<td>
<div className="form-flex-wrap">
<div className="select-wrap mr5">{/* <Select /> */}</div>
<div className="btn-area">
<span className="tb_ico change_check"></span>
</div>
</div>
</td>
<td>
<div className="form-flex-wrap">
<div className="name">HNW-MC4-CHN30</div>
<div className="icon-wrap">
<span className="tb_ico file_check"></span>
<button className="grid-tip"></button>
</div>
</div>
</td>
<td>
<div className="input-wrap" style={{ width: '100%' }}>
<input type="text" className="input-light al-r" defaultValue={'20'} />
</div>
</td>
<td>セット</td>
<td>
<div className="form-flex-wrap">
<div className="input-wrap mr5">
<input type="text" className="input-light al-r" defaultValue={'278,050'} />
</div>
<div className="btn-area">
<span className="tb_ico open_check"></span>
</div>
</div>
</td>
<td className="al-r">5,561,000</td>
</tr>
</tbody>
</table>
</div>
{/* html테이블끝 */} {/* html테이블끝 */}
</div> </div>
</div> </div>

View File

@ -5,6 +5,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom' import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom'
import { isObjectNotEmpty } from '@/util/common-utils' import { isObjectNotEmpty } from '@/util/common-utils'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
import { useMessage } from '@/hooks/useMessage'
const reducer = (prevState, nextState) => { const reducer = (prevState, nextState) => {
return { ...prevState, ...nextState } return { ...prevState, ...nextState }
@ -21,35 +22,33 @@ const defaultEstimateData = {
estimateType: 'YJOD', //주문분류 estimateType: 'YJOD', //주문분류
remarks: '', //비고 remarks: '', //비고
estimateOption: '', //견적특이사항 estimateOption: '', //견적특이사항
// itemList: [{ id: 1, name: '' }],
//아이템에 필요없는거 빼기 //아이템에 필요없는거 빼기
itemList: [ itemList: [
{ // {
amount: '', // amount: '',
fileUploadFlg: '', // fileUploadFlg: '',
itemChangeFlg: '', // itemChangeFlg: '',
itemGroup: '', // itemGroup: '',
itemId: '', //키값?? // itemId: '', //키값??
itemName: '', // itemName: '',
itemNo: '', // itemNo: '',
moduleFlg: '', // moduleFlg: '',
objectNo: '', // objectNo: '',
pkgMaterialFlg: '', // pkgMaterialFlg: '',
planNo: '', // planNo: '',
pnowW: '', // pnowW: '',
salePrice: '', // salePrice: '',
saleTotPrice: '', // saleTotPrice: '',
specification: '', // specification: '',
unit: '', // unit: '',
}, // },
], ],
fileList: [], fileList: [],
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
} }
// Helper functions // Helper functions
// const updateItemInList = (itemList, id, updates) => {
const updateItemInList = (itemList, itemId, updates) => { const updateItemInList = (itemList, itemId, updates) => {
// return itemList.map((item) => (item.id === id ? { ...item, ...updates } : item))
return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item)) return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item))
} }
@ -59,6 +58,8 @@ export const useEstimateController = (planNo) => {
const objectRecoil = useRecoilValue(floorPlanObjectState) const objectRecoil = useRecoilValue(floorPlanObjectState)
const [estimateData, setEstimateData] = useRecoilState(estimateState) const [estimateData, setEstimateData] = useRecoilState(estimateState)
const { getMessage } = useMessage()
const { get, post, promisePost } = useAxios(globalLocaleState) const { get, post, promisePost } = useAxios(globalLocaleState)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -87,20 +88,15 @@ export const useEstimateController = (planNo) => {
} }
} }
// const updateItem = (id, updates) => {
const updateItem = (itemId, updates) => { const updateItem = (itemId, updates) => {
setState({ setState({
// itemList: updateItemInList(state.itemList, id, updates),
itemList: updateItemInList(state.itemList, itemId, updates), itemList: updateItemInList(state.itemList, itemId, updates),
}) })
} }
const addItem = () => { const addItem = () => {
// const newId = Math.max(...state.itemList.map((item) => item.id)) + 1
const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1 const newItemId = Math.max(...state.itemList.map((item) => item.itemId)) + 1
setState({ setState({
// itemList: [...state.itemList, { id: newId, name: '' }],
//셋팅할필요없는거 빼기
itemList: [ itemList: [
...state.itemList, ...state.itemList,
{ {
@ -126,42 +122,55 @@ export const useEstimateController = (planNo) => {
} }
useEffect(() => { useEffect(() => {
setEstimateData({ ...state, userId: session.userId }) setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd: session.custCd })
//sapSalesStoreCd 추가예정 필수값
// setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd : session.sapSalesStoreCd })
}, [state]) }, [state])
//견적서 저장 //견적서 저장
const handleEstimateSubmit = async () => { const handleEstimateSubmit = async () => {
//0. 필수체크
let flag = true
console.log('::담긴 estimateData:::', estimateData) console.log('::담긴 estimateData:::', estimateData)
//1. 첨부파일 저장 //아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼
const formData = new FormData() if (estimateData.itemList.length > 1) {
formData.append('file', estimateData.fileList) estimateData.itemList.map((row) => {
formData.append('objectNo', estimateData.objectNo) if (row.fileUploadFlg === '1') {
formData.append('planNo', estimateData.planNo) if (estimateData.fileFlg === '0') {
formData.append('category', '10') alert(getMessage('estimate.detail.save.alertMsg'))
formData.append('userId', estimateData.userId) flag = false
for (const value of formData.values()) { }
console.log('formData::', value) }
}
await promisePost({ url: '/api/file/fileUpload', data: formData }).then((res) => {
console.log('파일저장::::::::::', res)
})
//2. 상세데이터 저장
console.log('상세저장시작!!')
return
try {
const result = await promisePost({
url: ESTIMATE_API_ENDPOINT,
data: estimateData,
}) })
return result }
} catch (error) { if (flag) {
console.error('Failed to submit estimate:', error) //1. 첨부파일 저장
throw error const formData = new FormData()
formData.append('file', estimateData.fileList)
formData.append('objectNo', estimateData.objectNo)
formData.append('planNo', estimateData.planNo)
formData.append('category', '10')
formData.append('userId', estimateData.userId)
for (const value of formData.values()) {
console.log('formData::', value)
}
await promisePost({ url: '/api/file/fileUpload', data: formData }).then((res) => {
console.log('파일저장결과::::::::::', res)
})
//2. 상세데이터 저장
console.log('상세저장시작!!')
return
try {
const result = await promisePost({
url: ESTIMATE_API_ENDPOINT,
data: estimateData,
})
return result
} catch (error) {
console.error('Failed to submit estimate:', error)
throw error
}
} }
} }

View File

@ -818,7 +818,7 @@
"estimate.detail.estimateType": "注文分類", "estimate.detail.estimateType": "注文分類",
"estimate.detail.roofCns": "屋根材・仕様施工", "estimate.detail.roofCns": "屋根材・仕様施工",
"estimate.detail.remarks": "備考", "estimate.detail.remarks": "備考",
"estimate.detail.nextSubmit": "後日資料提出", "estimate.detail.fileFlg": "後日資料提出",
"estimate.detail.header.fileList1": "ファイル添付", "estimate.detail.header.fileList1": "ファイル添付",
"estimate.detail.fileList.btn": "ファイル選択", "estimate.detail.fileList.btn": "ファイル選択",
"estimate.detail.header.fileList2": "添付ファイル一覧", "estimate.detail.header.fileList2": "添付ファイル一覧",
@ -835,11 +835,20 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000", "estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)", "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)",
"estimate.detail.header.showPrice": "価格表示", "estimate.detail.header.showPrice": "価格表示",
"estimate.detail.showPrice.btn1": "Pricing", "estimate.detail.header.unitPrice": "定価",
"estimate.detail.showPrice.pricingBtn": "Pricing",
"estimate.detail.showPrice.description1": "製品価格 OPEN", "estimate.detail.showPrice.description1": "製品価格 OPEN",
"estimate.detail.showPrice.description2": "追加, 変更資材", "estimate.detail.showPrice.description2": "追加, 変更資材",
"estimate.detail.showPrice.description3": "添付必須", "estimate.detail.showPrice.description3": "添付必須",
"estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する", "estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する",
"estimate.detail.showPrice.btn2": "製品を追加", "estimate.detail.showPrice.btn2": "製品を追加",
"estimate.detail.showPrice.btn3": "製品削除" "estimate.detail.showPrice.btn3": "製品削除",
"estimate.detail.itemTableHeader.col1": "アイテム",
"estimate.detail.itemTableHeader.col2": "品番",
"estimate.detail.itemTableHeader.col3": "型板",
"estimate.detail.itemTableHeader.col4": "数量",
"estimate.detail.itemTableHeader.col5": "単位",
"estimate.detail.itemTableHeader.col6": "単価",
"estimate.detail.itemTableHeader.col7": "金額 (税別別)",
"estimate.detail.save.alertMsg": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください."
} }

View File

@ -824,7 +824,7 @@
"estimate.detail.estimateType": "주문분류", "estimate.detail.estimateType": "주문분류",
"estimate.detail.roofCns": "지붕재・사양시공", "estimate.detail.roofCns": "지붕재・사양시공",
"estimate.detail.remarks": "비고", "estimate.detail.remarks": "비고",
"estimate.detail.nextSubmit": "후일자료제출", "estimate.detail.fileFlg": "후일자료제출",
"estimate.detail.header.fileList1": "파일첨부", "estimate.detail.header.fileList1": "파일첨부",
"estimate.detail.fileList.btn": "파일선택", "estimate.detail.fileList.btn": "파일선택",
"estimate.detail.header.fileList2": "첨부파일 목록", "estimate.detail.header.fileList2": "첨부파일 목록",
@ -841,11 +841,20 @@
"estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100", "estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100",
"estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)", "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)",
"estimate.detail.header.showPrice": "가격표시", "estimate.detail.header.showPrice": "가격표시",
"estimate.detail.showPrice.btn1": "Pricing", "estimate.detail.header.unitPrice": "정가",
"estimate.detail.showPrice.pricingBtn": "Pricing",
"estimate.detail.showPrice.description1": "제품 가격 OPEN", "estimate.detail.showPrice.description1": "제품 가격 OPEN",
"estimate.detail.showPrice.description2": "추가, 변경 자재", "estimate.detail.showPrice.description2": "추가, 변경 자재",
"estimate.detail.showPrice.description3": "첨부필수", "estimate.detail.showPrice.description3": "첨부필수",
"estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인", "estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인",
"estimate.detail.showPrice.btn2": "제품추가", "estimate.detail.showPrice.btn2": "제품추가",
"estimate.detail.showPrice.btn3": "제품삭제" "estimate.detail.showPrice.btn3": "제품삭제",
"estimate.detail.itemTableHeader.col1": "Item",
"estimate.detail.itemTableHeader.col2": "품번",
"estimate.detail.itemTableHeader.col3": "형명",
"estimate.detail.itemTableHeader.col4": "수량",
"estimate.detail.itemTableHeader.col5": "단위",
"estimate.detail.itemTableHeader.col6": "단가",
"estimate.detail.itemTableHeader.col7": "금액(부가세별도)",
"estimate.detail.save.alertMsg": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오."
} }