견적서 상세 화면 작업중

This commit is contained in:
basssy 2024-11-04 17:54:34 +09:00
parent c7549b243a
commit 187f11df19
4 changed files with 779 additions and 244 deletions

View File

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

View File

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

View File

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

595
yarn.lock

File diff suppressed because it is too large Load Diff