keyy1315 333943c651 feat: add SUBMISSION_TARGET_ID, SRL_NO Column
- 제출 대상 판매점 ID, 일련번호 컬럼 추가
- 임시 저장 시 일련번호에 '임시저장000' 으로 저장
- 조사매물 목록 조회 필터링 조건 수정
- url 에러 핸들링
2025-05-21 17:53:58 +09:00

758 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react'
import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
type RadioEtcKeys = 'houseStructure' | 'rafterMaterial' | 'waterproofMaterial' | 'insulationPresence' | 'rafterDirection' | 'leakTrace'
type SelectBoxKeys =
| 'installationSystem'
| 'constructionYear'
| 'roofShape'
| 'rafterPitch'
| 'rafterSize'
| 'openFieldPlateKind'
| 'structureOrder'
| 'installationAvailability'
export const supplementaryFacilities = [
{ id: 1, name: 'エコキュート' }, //에코큐트
{ id: 2, name: 'エネパーム' }, //에네팜
{ id: 3, name: '蓄電池システム' }, //축전지시스템
{ id: 4, name: '太陽光発電' }, //태양광발전
]
export const roofMaterial = [
{ id: 1, name: 'スレート' }, //슬레이트
{ id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글
{ id: 3, name: '瓦' }, //기와
{ id: 4, name: '金属屋根' }, //금속지붕
]
export const selectBoxOptions: Record<SelectBoxKeys, { id: number; name: string }[]> = {
installationSystem: [
{
id: 1,
name: '太陽光発電', //태양광발전
},
{
id: 2,
name: 'ハイブリッド蓄電システム', //하이브리드축전지시스템
},
{
id: 3,
name: '蓄電池システム', //축전지시스템
},
],
constructionYear: [
{
id: 1,
name: '新築', //신축
},
{
id: 2,
name: '既築', //기존
},
],
roofShape: [
{
id: 1,
name: '切妻', //박공지붕
},
{
id: 2,
name: '寄棟', //기동
},
{
id: 3,
name: '片流れ', //한쪽흐름
},
],
rafterSize: [
{
id: 1,
name: '幅35mm以上×高さ48mm以上',
},
{
id: 2,
name: '幅36mm以上×高さ46mm以上',
},
{
id: 3,
name: '幅37mm以上×高さ43mm以上',
},
{
id: 4,
name: '幅38mm以上×高さ40mm以上',
},
],
rafterPitch: [
{
id: 1,
name: '455mm以下',
},
{
id: 2,
name: '500mm以下',
},
{
id: 3,
name: '606mm以下',
},
],
openFieldPlateKind: [
{
id: 1,
name: '構造用合板', //구조용합판
},
{
id: 2,
name: 'OSB', //OSB
},
{
id: 3,
name: 'パーティクルボード', //파티클보드
},
{
id: 4,
name: '小幅板', //소판
},
],
structureOrder: [
{
id: 1,
name: '屋根材', //지붕재
},
{
id: 2,
name: '防水材', //방수재
},
{
id: 3,
name: '屋根の基礎', //지붕의기초
},
{
id: 4,
name: '垂木', //서까래
},
],
installationAvailability: [
{
id: 1,
name: '確認済み', //확인완료
},
{
id: 2,
name: '未確認', //미확인
},
],
}
export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = {
houseStructure: [
{
id: 1,
label: '木製',
},
],
rafterMaterial: [
{
id: 1,
label: '木製',
},
{
id: 2,
label: '強制',
},
],
waterproofMaterial: [
{
id: 1,
label: 'アスファルト屋根94022kg以上',
},
],
insulationPresence: [
{
id: 1,
label: 'なし',
},
{
id: 2,
label: 'あり',
},
],
rafterDirection: [
{
id: 1,
label: '垂直垂木',
},
{
id: 2,
label: '水平垂木',
},
],
leakTrace: [
{
id: 1,
label: 'あり',
},
{
id: 2,
label: 'なし',
},
],
}
const makeNumArr = (value: string) => {
return value
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
}
export default function RoofForm(props: {
roofInfo: SurveyDetailRequest | SurveyDetailInfo
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
mode: Mode
}) {
const { roofInfo, setRoofInfo, mode } = props
const [isFlip, setIsFlip] = useState<boolean>(true)
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
if (key === 'roofSlope' || key === 'openFieldPlateThickness') {
const stringValue = value.toString()
if (stringValue.length > 5) {
alert('保存できるサイズを超えました。')
return
}
if (stringValue.includes('.')) {
const decimalPlaces = stringValue.split('.')[1].length
if (decimalPlaces > 1) {
alert('小数点以下1桁までしか許されません。')
return
}
}
}
if (key === 'contractCapacity') {
const remainValue = roofInfo.contractCapacity?.split(' ')[1] ?? roofInfo.contractCapacity
if (Number.isNaN(Number(remainValue))) {
setRoofInfo({ ...roofInfo, [key]: value + ' ' + remainValue })
return
}
setRoofInfo({ ...roofInfo, [key]: value.toString() })
}
setRoofInfo({ ...roofInfo, [key]: value.toString() })
}
const handleUnitInput = (value: string) => {
const numericValue = roofInfo.contractCapacity?.replace(/[^0-9.]/g, '') || ''
setRoofInfo({
...roofInfo,
contractCapacity: numericValue ? `${numericValue} ${value}` : '0 ' + value,
})
}
return (
<div className={`sale-detail-toggle-bx ${isFlip ? 'act' : ''}`}>
<div className="sale-detail-toggle-head" onClick={() => setIsFlip(!isFlip)}>
<div className="sale-detail-toggle-name"> / </div>
<div className="sale-detail-toggle-btn-wrap">
<button className="sale-detail-toggle-btn"></button>
</div>
</div>
<div className="sale-detail-toggle-cont">
<div className="sale-frame">
{/* 전기 관계 */}
<div className="sale-roof-title"></div>
<div className="data-form-wrap">
<div className="data-input-form-bx">
{/* 전기 계약 용량 */}
<div className="data-input-form-tit"></div>
{mode === 'READ' && <input type="text" className="input-frame" value={roofInfo?.contractCapacity ?? ''} disabled={mode === 'READ'} />}
{mode !== 'READ' && (
<div className="data-input mb5">
<input
type="number"
id="contractCapacity"
className="input-frame"
value={roofInfo?.contractCapacity?.split(' ')[0] ?? ''}
onChange={(e) => handleNumberInput('contractCapacity', e.target.value)}
/>
<div className="data-input">
<select
className="select-form"
name="contractCapacityUnit"
id="contractCapacityUnit"
value={roofInfo?.contractCapacity?.split(' ')[1] ?? ''}
onChange={(e) => handleUnitInput(e.target.value)}
>
<option value="" hidden>
</option>
<option value="A">A</option>
<option value="kVA">kVA</option>
</select>
</div>
</div>
)}
</div>
<div className="data-input-form-bx">
{/* 전기 소매 회사사 */}
<div className="data-input-form-tit"></div>
<input
type="text"
className="input-frame"
value={roofInfo?.retailCompany ?? ''}
disabled={mode === 'READ'}
onChange={(e) => setRoofInfo({ ...roofInfo, retailCompany: e.target.value })}
/>
</div>
<div className="data-input-form-bx">
{/* 전기 부대 설비 */}
<div className="data-input-form-tit">
<span></span>
</div>
<MultiCheck mode={mode} column="supplementaryFacilities" roofInfo={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<SelectedBox mode={mode} column="installationSystem" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
</div>
<div className="sale-frame">
{/* 지붕 관계 */}
<div className="sale-roof-title"></div>
<div className="data-form-wrap">
<div className="data-input-form-bx">
{/* 건축 연수 */}
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<SelectedBox mode={mode} column="constructionYear" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 지붕재 */}
<div className="data-input-form-tit">
<span>2</span>
</div>
<MultiCheck mode={mode} column="roofMaterial" roofInfo={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 지붕 모양 */}
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<SelectedBox mode={mode} column="roofShape" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 지붕 경사도도 */}
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input
type="number"
className="input-frame"
value={roofInfo?.roofSlope ?? ''}
disabled={mode === 'READ'}
onChange={(e) => handleNumberInput('roofSlope', e.target.value)}
/>
<span></span>
</div>
</div>
<div className="data-input-form-bx">
{/* 주택구조조 */}
<div className="data-input-form-tit"></div>
<RadioSelected mode={mode} column="houseStructure" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 서까래 재질 */}
<div className="data-input-form-tit"></div>
<RadioSelected mode={mode} column="rafterMaterial" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 서까래 크기 */}
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<SelectedBox mode={mode} column="rafterSize" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 서까래 피치 */}
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<SelectedBox mode={mode} column="rafterPitch" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 서까래 방향 */}
<div className="data-input-form-tit red-f"></div>
<div className="data-check-wrap mb0">
<RadioSelected mode={mode} column="rafterDirection" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 노지판 종류류 */}
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<SelectedBox mode={mode} column="openFieldPlateKind" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 노지판 두께 */}
<div className="data-input-form-tit">
<span>, . </span>
</div>
<div className="data-input flex">
<input
type="text"
className="input-frame"
value={roofInfo?.openFieldPlateThickness ?? ''}
disabled={mode === 'READ'}
onChange={(e) => handleNumberInput('openFieldPlateThickness', e.target.value)}
/>
<span>mm</span>
</div>
</div>
<div className="data-input-form-bx">
{/* 누수 흔적 */}
<div className="data-input-form-tit "></div>
<div className="data-check-wrap mb0">
<RadioSelected mode={mode} column="leakTrace" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
</div>
<div className="data-input-form-bx">
{/* 방수재 종류 */}
<div className="data-input-form-tit red-f"></div>
<RadioSelected mode={mode} column="waterproofMaterial" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 단열재 유무 */}
<div className="data-input-form-tit red-f"></div>
<RadioSelected mode={mode} column="insulationPresence" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 지붕 구조의 순서 */}
<div className="data-input-form-tit red-f"></div>
<SelectedBox mode={mode} column="structureOrder" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 지붕 제품명 설치 가능 여부 확인 */}
<div className="data-input-form-tit"> </div>
<SelectedBox mode={mode} column="installationAvailability" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
</div>
<div className="data-input-form-bx">
{/* 메모 */}
<div className="data-input-form-tit"></div>
<div className="data-input">
<textarea
className="textarea-form"
name="memo"
id="memo"
placeholder="TextArea Filed"
value={roofInfo?.memo ?? ''}
disabled={mode === 'READ'}
onChange={(e) => setRoofInfo({ ...roofInfo, memo: e.target.value })}
></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
const SelectedBox = ({
mode,
column,
detailInfoData,
setRoofInfo,
}: {
mode: Mode
column: string
detailInfoData: SurveyDetailInfo
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
}) => {
const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
const [isEtcSelected, setIsEtcSelected] = useState<boolean>(Boolean(etcValue))
const isSpecialCase = column === 'constructionYear' || column === 'installationAvailability'
const showEtcOption = !isSpecialCase
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value
const isEtc = value === 'etc'
const isSpecialEtc = isSpecialCase && value === '2'
const updatedData = {
...detailInfoData,
[column]: isEtc ? null : value,
[`${column}Etc`]: isEtc ? '' : null,
}
if (isSpecialEtc) {
updatedData[column] = value
}
setIsEtcSelected(isEtc || isSpecialEtc)
setRoofInfo(updatedData)
}
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setRoofInfo({ ...detailInfoData, [`${column}Etc`]: e.target.value })
}
const isInputDisabled = () => {
if (mode === 'READ') return true
if (column === 'installationAvailability') return false
if (column === 'constructionYear') {
return detailInfoData.constructionYear === '1' || detailInfoData.constructionYear === null
}
return !isEtcSelected && !etcValue
}
return (
<>
<select
className="select-form mb10"
name={column}
id={column}
disabled={mode === 'READ'}
value={selectedId ? Number(selectedId) : etcValue ? 'etc' : ''}
onChange={handleSelectChange}
>
{selectBoxOptions[column as keyof typeof selectBoxOptions].map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
{showEtcOption && (
<option key="etc" value="etc">
()
</option>
)}
<option key="" value="" hidden>
</option>
</select>
<div className={`data-input ${column === 'constructionYear' ? 'flex' : ''}`}>
<input
type={column === 'constructionYear' ? 'number' : 'text'}
className="input-frame"
placeholder="-"
value={detailInfoData[`${column}Etc` as keyof SurveyDetailInfo]?.toString() ?? ''}
onChange={handleEtcInputChange}
disabled={isInputDisabled()}
/>
{column === 'constructionYear' && <span></span>}
</div>
</>
)
}
const RadioSelected = ({
mode,
column,
detailInfoData,
setRoofInfo,
}: {
mode: Mode
column: string
detailInfoData: SurveyDetailInfo
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
}) => {
const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
const [etcChecked, setEtcChecked] = useState<boolean>(Boolean(etcValue))
const selectedId =
column === 'leakTrace' ? Number(detailInfoData?.[column as keyof SurveyDetailInfo]) || 2 : detailInfoData?.[column as keyof SurveyDetailInfo]
const isSpecialColumn = column === 'rafterDirection' || column === 'leakTrace' || column === 'insulationPresence'
const showEtcOption = !isSpecialColumn
const handleRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
if (column === 'leakTrace') {
setRoofInfo({ ...detailInfoData, leakTrace: value === '1' })
return
}
if (value === 'etc') {
setEtcChecked(true)
setRoofInfo({ ...detailInfoData, [column]: null, [`${column}Etc`]: '' })
return
}
const isInsulationPresence = column === 'insulationPresence'
const isRafterDirection = column === 'rafterDirection'
setEtcChecked(isInsulationPresence && value === '2')
setRoofInfo({
...detailInfoData,
[column]: value,
[`${column}Etc`]: isRafterDirection ? detailInfoData[`${column}Etc` as keyof SurveyDetailInfo] : null,
})
}
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setRoofInfo({ ...detailInfoData, [`${column}Etc`]: e.target.value })
}
const isInputDisabled = () => {
if (mode === 'READ') return true
if (column === 'insulationPresence') {
return detailInfoData.insulationPresence !== '2'
}
return !etcChecked && !etcValue
}
return (
<>
{radioEtcData[column as keyof typeof radioEtcData].map((item) => (
<div className="radio-form-box mb10" key={item.id}>
<input
type="radio"
name={column}
id={`${column}_${item.id}`}
disabled={mode === 'READ'}
checked={Number(selectedId) === item.id}
onChange={handleRadioChange}
value={item.id}
/>
<label htmlFor={`${column}_${item.id}`}>{item.label}</label>
</div>
))}
{showEtcOption && (
<div className="radio-form-box mb10">
<input
type="radio"
name={column}
id={`${column}Etc`}
value="etc"
disabled={mode === 'READ'}
checked={etcChecked || Boolean(etcValue)}
onChange={handleRadioChange}
/>
<label htmlFor={`${column}Etc`}> ()</label>
</div>
)}
{showEtcOption && (
<div className="data-input">
<input
type="text"
className="input-frame"
placeholder="-"
value={detailInfoData[`${column}Etc` as keyof SurveyDetailInfo]?.toString() ?? ''}
onChange={handleEtcInputChange}
disabled={isInputDisabled()}
/>
</div>
)}
</>
)
}
const MultiCheck = ({
mode,
column,
roofInfo,
setRoofInfo,
}: {
mode: Mode
column: string
roofInfo: SurveyDetailInfo
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
}) => {
const multiCheckData = column === 'supplementaryFacilities' ? supplementaryFacilities : roofMaterial
const etcValue = roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo]
const [isOtherCheck, setIsOtherCheck] = useState<boolean>(Boolean(etcValue))
const isRoofMaterial = column === 'roofMaterial'
const selectedValues = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? ''))
const handleCheckbox = (id: number) => {
const isOtherSelected = Boolean(etcValue)
let newValue: string[]
if (selectedValues.includes(String(id))) {
newValue = selectedValues.filter((v) => v !== String(id))
} else {
if (isRoofMaterial) {
const totalSelected = selectedValues.length + (isOtherSelected ? 1 : 0)
if (totalSelected >= 2) {
alert('屋根材は最大2個まで選択できます。')
return
}
}
newValue = [...selectedValues, String(id)]
}
setRoofInfo({ ...roofInfo, [column]: newValue.join(',') })
}
const handleOtherCheckbox = () => {
if (isRoofMaterial) {
const currentSelected = selectedValues.length
if (!isOtherCheck && currentSelected >= 2) {
alert('屋根材は最大2個まで選択できます。')
return
}
}
const newIsOtherCheck = !isOtherCheck
setIsOtherCheck(newIsOtherCheck)
// 기타 선택 해제 시 값도 null로 설정
setRoofInfo({
...roofInfo,
[`${column}Etc`]: newIsOtherCheck ? '' : null,
})
}
const handleOtherInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setRoofInfo({ ...roofInfo, [`${column}Etc`]: e.target.value })
}
const isInputDisabled = () => {
return mode === 'READ' || (!isOtherCheck && !etcValue)
}
return (
<>
<div className="data-check-wrap">
{multiCheckData.map((item) => (
<div className="check-form-box" key={item.id}>
<input
type="checkbox"
id={`${column}_${item.id}`}
checked={selectedValues.includes(String(item.id))}
disabled={mode === 'READ'}
onChange={() => handleCheckbox(item.id)}
/>
<label htmlFor={`${column}_${item.id}`}>{item.name}</label>
</div>
))}
<div className="check-form-box">
<input
type="checkbox"
id={`${column}Etc`}
checked={isOtherCheck || Boolean(etcValue)}
disabled={mode === 'READ'}
onChange={handleOtherCheckbox}
/>
<label htmlFor={`${column}Etc`}> ()</label>
</div>
</div>
<div className="data-input">
<input
type="text"
className="input-frame"
placeholder="-"
value={roofInfo[`${column}Etc` as keyof SurveyDetailInfo]?.toString() ?? ''}
onChange={handleOtherInputChange}
disabled={isInputDisabled()}
/>
</div>
</>
)
}