feat: implement create & update survey roof-information with validate

- 조사 매물 지붕 정보 필수값 적용하여 등록 & 수정 가능하도록 구현
- 조사 매물 상세 & 작성 페이지 Nav 탭 기본 정보 미등록 시 라우팅 안되도록 설정정
This commit is contained in:
Dayoung 2025-05-07 18:05:28 +09:00
parent daf9f31733
commit fd27bfe7d0
16 changed files with 1000 additions and 542 deletions

View File

@ -133,7 +133,7 @@ model SD_SERVEY_SALES_DETAIL_INFO {
//전기 소매 회사
retail_company String? @db.VarChar(100)
//전기 부대 설비
supplementary_facilities Int? @db.Int
supplementary_facilities String? @db.VarChar(20)
//전기 부대 설비 기타
supplementary_facilities_etc String? @db.VarChar(200)
//설치 희망 시스템
@ -145,7 +145,7 @@ model SD_SERVEY_SALES_DETAIL_INFO {
//건축 연수 기타
construction_year_etc String? @db.VarChar(200)
//지붕재
roof_material Int? @db.Int
roof_material String? @db.VarChar(20)
//지붕재 기타
roof_material_etc String? @db.VarChar(200)
//지붕 모양

View File

@ -1,4 +1,4 @@
import BasicForm from '@/components/survey-sale/detail/BasicForm'
import BasicForm from '@/components/survey-sale/detail/form/BasicForm'
export default function page() {
return (

View File

@ -4,7 +4,6 @@ import SearchForm from '@/components/survey-sale/list/SearchForm'
export default function page() {
return (
<>
<SearchForm />
<ListTable />
</>
)

View File

@ -1,4 +1,4 @@
import RoofInfoForm from '@/components/survey-sale/detail/RoofInfoForm'
import RoofInfoForm from '@/components/survey-sale/detail/form/RoofInfoForm'
export default function page() {
return (

View File

@ -1,16 +1,19 @@
'use client'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { usePathname, useRouter } from 'next/navigation'
import { usePathname, useRouter, useSearchParams, useParams } from 'next/navigation'
import { useEffect } from 'react'
import { usePopupController } from '@/store/popupController'
export default function NavTab() {
const router = useRouter()
const pathname = usePathname()
if (pathname === '/survey-sale') {
return null
}
const searchParams = useSearchParams()
const id = searchParams.get('id')
const params = useParams()
const detailId = params.id
const { basicInfoSelected, roofInfoSelected, reset } = useSurveySaleTabState()
@ -18,14 +21,37 @@ export default function NavTab() {
return () => {
reset()
}
}, [])
}, [reset])
if (pathname === '/survey-sale') {
return null
}
const handleBasicInfoClick = () => {
router.push('/survey-sale/basic-info')
if (id) {
router.push(`/survey-sale/basic-info?id=${id}`)
return
}
if (detailId) {
router.push(`/survey-sale/${detailId}?tab=basic-info`)
return
}
}
const handleRoofInfoClick = () => {
router.push('/survey-sale/roof-info')
if (id) {
router.push(`/survey-sale/roof-info?id=${id}`)
return
}
if (detailId) {
router.push(`/survey-sale/${detailId}?tab=roof-info`)
return
}
if (pathname === '/survey-sale/basic-info') {
// TODO: 팝업 추가
alert('Save essential information first')
return null
}
}
return (

View File

@ -2,12 +2,21 @@
import { useServey } from '@/hooks/useSurvey'
import { useParams } from 'next/navigation'
import { useEffect } from 'react'
import { useState } from 'react'
export default function DataTable() {
const params = useParams()
const id = params.id
const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id))
const [isTemporary, setIsTemporary] = useState(false)
useEffect(() => {
if (!surveyDetail?.representative || !surveyDetail?.store || !surveyDetail?.construction_point) {
setIsTemporary(true)
}
}, [surveyDetail])
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
@ -24,7 +33,13 @@ export default function DataTable() {
<tbody>
<tr>
<th></th>
<td>{surveyDetail?.id}</td>
{isTemporary ? (
<td>
<span className="text-red-500"></span>
</td>
) : (
<td>{surveyDetail?.id}</td>
)}
</tr>
<tr>
<th></th>
@ -39,7 +54,7 @@ export default function DataTable() {
<td>
{surveyDetail?.submission_status && surveyDetail?.submission_date
? new Date(surveyDetail.submission_date).toLocaleString()
: '未提出'}
: '-'}
</td>
</tr>
<tr>

View File

@ -1,15 +1,37 @@
'use client'
import { useServey } from '@/hooks/useSurvey'
import { useParams, useRouter } from 'next/navigation'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { useEffect, useState } from 'react'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
export default function DetailForm() {
const router = useRouter()
const params = useParams()
const id = params.id
const { surveyDetail, deleteSurvey, submitSurvey, isLoadingSurveyDetail } = useServey(Number(id))
const searchParams = useSearchParams()
const tab = searchParams.get('tab')
const { surveyDetail, deleteSurvey, submitSurvey, isLoadingSurveyDetail } = useServey(Number(id))
const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState()
const [isTemporary, setIsTemporary] = useState(true)
// TODO: 조사매물 지붕정보 퍼블 구현되면 탭 화면 변경 추가
useEffect(() => {
if (tab === 'basic-info') {
setBasicInfoSelected()
}
if (tab === 'roof-info') {
setRoofInfoSelected()
}
if (surveyDetail?.representative && surveyDetail?.store && surveyDetail?.construction_point) {
setIsTemporary(false)
}
}, [tab, setBasicInfoSelected, setRoofInfoSelected, surveyDetail])
console.log('isTemporary', isTemporary)
console.log('surveyDetail', surveyDetail)
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
}
@ -68,16 +90,22 @@ export default function DetailForm() {
</div>
</div>
<div className="btn-flex-wrap">
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSubmit}>
<i className="btn-arr"></i>
</button>
</div>
{isTemporary ? (
<></>
) : (
<>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSubmit}>
<i className="btn-arr"></i>
</button>
</div>
</>
)}
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleUpdate}>
<i className="btn-arr"></i>

View File

@ -1,415 +0,0 @@
'use client'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { useServey } from '@/hooks/useSurvey'
import { SurveyDetailRequest } from '@/types/Survey'
import { useRouter, useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import MultiCheckbox from './form/MultiCheckbox'
const defaultDetailInfoForm: SurveyDetailRequest = {
contract_capacity: null,
retail_company: null,
supplementary_facilities: null,
supplementary_facilities_etc: null,
installation_system: null,
installation_system_etc: null,
construction_year: null,
construction_year_etc: null,
roof_material: null,
roof_material_etc: null,
roof_shape: null,
roof_shape_etc: null,
roof_slope: null,
house_structure: null,
house_structure_etc: null,
rafter_material: null,
rafter_material_etc: null,
rafter_size: null,
rafter_size_etc: null,
rafter_pitch: null,
rafter_pitch_etc: null,
rafter_direction: null,
open_field_plate_kind: null,
open_field_plate_kind_etc: null,
open_field_plate_thickness: null,
leak_trace: null,
waterproof_material: null,
waterproof_material_etc: null,
insulation_presence: null,
insulation_presence_etc: null,
structure_order: null,
structure_order_etc: null,
installation_availability: null,
installation_availability_etc: null,
memo: null,
}
export default function RoofInfoForm() {
const { setRoofInfoSelected } = useSurveySaleTabState()
useEffect(() => {
setRoofInfoSelected()
}, [])
const router = useRouter()
const searchParams = useSearchParams()
const id = searchParams.get('id')
const { updateSurvey, isUpdatingSurvey, surveyDetail, createSurveyDetail } = useServey(Number(id))
const [detailInfoData, setDetailInfoData] = useState<SurveyDetailRequest>(defaultDetailInfoForm)
useEffect(() => {
if (surveyDetail?.detail_info) {
const { id, updated_at, created_at, ...rest } = surveyDetail.detail_info
setDetailInfoData(rest)
}
}, [surveyDetail])
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
if (typeof value === 'string') {
const numberValue = value === '' ? null : Number(value)
setDetailInfoData({ ...detailInfoData, [key]: numberValue })
} else {
setDetailInfoData({ ...detailInfoData, [key]: value })
}
}
const handleTextInput = (key: keyof SurveyDetailRequest, value: string) => {
setDetailInfoData({ ...detailInfoData, [key]: value || null })
}
const handleBooleanInput = (key: keyof SurveyDetailRequest, checked: boolean) => {
setDetailInfoData({ ...detailInfoData, [key]: checked })
}
const handleUnitInput = (value: string) => {
const capacity = detailInfoData.contract_capacity
setDetailInfoData({ ...detailInfoData, contract_capacity: `${capacity} ${value}` })
}
const handleSave = () => {
if (id) {
console.log('detailInfoData:: ', detailInfoData)
}
}
return (
<>
<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>
<div className="data-input mb5">
<input
type="text"
className="input-frame"
value={detailInfoData.contract_capacity ?? ''}
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
/>
</div>
<div className="data-input">
<select
className="select-form"
name="contract_capacity_unit"
id="contract_capacity_unit"
onChange={(e) => handleUnitInput(e.target.value)}
>
<option value="kVA">kVA</option>
<option value="">kVA</option>
<option value="">kVA</option>
<option value="">kVA</option>
<option value="">kVA</option>
</select>
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<input
type="text"
className="input-frame"
value={detailInfoData.retail_company ?? ''}
onChange={(e) => handleTextInput('retail_company', e.target.value)}
/>
</div>
<div className="data-input-form-bx">
<MultiCheckbox column={'supplementary_facilities'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData}/>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={''} disabled />
</div>
</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">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input flex">
<input type="text" className="input-frame" defaultValue={''} disabled />
<span></span>
</div>
</div>
<div className="data-input-form-bx">
<MultiCheckbox column={'roof_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={''} disabled />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input type="text" className="input-frame" defaultValue={'4'} />
<span></span>
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="radio-form-box mb10">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01"></label>
</div>
<div className="radio-form-box mb10">
<input type="radio" name="radio01" id="ra02" />
<label htmlFor="ra02"> ()</label>
</div>
<div className="data-input">
<input type="text" className="input-frame" disabled defaultValue={''} />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-check-wrap">
<div className="radio-form-box">
<input type="radio" name="radio02" id="ra03" />
<label htmlFor="ra03"></label>
</div>
<div className="radio-form-box">
<input type="radio" name="radio02" id="ra04" />
<label htmlFor="ra04"></label>
</div>
<div className="radio-form-box">
<input type="radio" name="radio02" id="ra05" />
<label htmlFor="ra05"> ()</label>
</div>
</div>
<div className="data-input">
<input type="text" className="input-frame" disabled defaultValue={''} />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value="">35mm以上×48mm以上</option>
<option value="">35mm以上×48mm以上</option>
<option value="">35mm以上×48mm以上</option>
<option value="">35mm以上×48mm以上</option>
<option value="">35mm以上×48mm以上</option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={''} disabled />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value="">455mm以下</option>
<option value="">455mm以下</option>
<option value="">455mm以下</option>
<option value="">455mm以下</option>
<option value="">455mm以下</option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={''} disabled />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="data-check-wrap mb0">
<div className="radio-form-box">
<input type="radio" name="radio03" id="ra06" />
<label htmlFor="ra06"></label>
</div>
<div className="radio-form-box">
<input type="radio" name="radio03" id="ra07" />
<label htmlFor="ra07"></label>
</div>
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={''} disabled />
</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" defaultValue={'150'} />
<span>mm</span>
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit "></div>
<div className="data-check-wrap mb0">
<div className="radio-form-box">
<input type="radio" name="radio04" id="ra08" />
<label htmlFor="ra08"></label>
</div>
<div className="radio-form-box">
<input type="radio" name="radio04" id="ra09" />
<label htmlFor="ra09"></label>
</div>
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="radio-form-box mb10">
<input type="radio" name="radio05" id="ra10" />
<label htmlFor="ra10">94022kg以上</label>
</div>
<div className="radio-form-box mb10">
<input type="radio" name="radio05" id="ra11" />
<label htmlFor="ra11"> ()</label>
</div>
<div className="data-input">
<input type="text" className="input-frame" disabled defaultValue={''} />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="radio-form-box mb10">
<input type="radio" name="radio06" id="ra12" />
<label htmlFor="ra12"></label>
</div>
<div className="data-input mb10">
<input type="text" className="input-frame" defaultValue={''} />
</div>
<div className="radio-form-box">
<input type="radio" name="radio06" id="ra13" />
<label htmlFor="ra13"></label>
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value="">///</option>
<option value="">///</option>
<option value="">///</option>
<option value="">///</option>
<option value="">///</option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={''} disabled />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"> </div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input">
<input type="text" className="input-frame" defaultValue={'高島'} />
</div>
</div>
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input">
<textarea
className="textarea-form"
name=""
id=""
defaultValue={'漏れの兆候があるため、正確な点検が必要です.'}
placeholder="TextArea Filed"
></textarea>
</div>
</div>
</div>
<div className="btn-flex-wrap">
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleSave}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSave}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
</div>
</div>
</>
)
}

View File

@ -20,10 +20,14 @@ const defaultBasicInfoForm: SurveyBasicRequest = {
submission_date: null,
}
const REQUIRED_FIELDS: (keyof SurveyBasicRequest)[] = ['representative', 'store', 'construction_point']
export default function BasicForm() {
const searchParams = useSearchParams()
const id = searchParams.get('id')
const router = useRouter()
const { setBasicInfoSelected } = useSurveySaleTabState()
const { surveyDetail, createSurvey, isCreatingSurvey, updateSurvey, isUpdatingSurvey } = useServey(Number(id))
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(defaultBasicInfoForm)
@ -35,31 +39,53 @@ export default function BasicForm() {
}
}, [surveyDetail])
useEffect(() => {
setBasicInfoSelected()
}, [])
const focusInput = (input: keyof SurveyBasicRequest) => {
const inputElement = document.getElementById(input)
if (inputElement) {
inputElement.focus()
}
}
const validateSurvey = (basicInfoData: SurveyBasicRequest) => {
const emptyField = REQUIRED_FIELDS.find((field) => !basicInfoData[field])
if (emptyField) {
focusInput(emptyField)
return false
}
return true
}
const handleChange = (key: keyof SurveyBasicRequest, value: string) => {
setBasicInfoData({ ...basicInfoData, [key]: value })
}
const router = useRouter()
const handleSave = () => {
const handleSave = async (isTemporary: boolean) => {
if (id) {
console.log('basicInfoData:: ', basicInfoData)
updateSurvey(basicInfoData)
} else {
createSurvey(basicInfoData)
router.push(`/survey-sale/${id}?tab=basic-info`)
}
if (isTemporary) {
const saveId = await createSurvey(basicInfoData)
alert('save success temporary id: ' + saveId)
router.push(`/survey-sale/${saveId}?tab=basic-info`)
} else {
if (validateSurvey(basicInfoData)) {
const saveId = await createSurvey(basicInfoData)
alert('save success id: ' + saveId)
router.push(`/survey-sale/${saveId}?tab=basic-info`)
}
}
router.push('/survey-sale')
}
if (isCreatingSurvey || isUpdatingSurvey) {
return <div>Loading...</div>
}
const { setBasicInfoSelected } = useSurveySaleTabState()
useEffect(() => {
setBasicInfoSelected()
}, [])
return (
<>
<div className="sale-frame">
@ -72,6 +98,7 @@ export default function BasicForm() {
id="representative"
value={basicInfoData.representative}
onChange={(e) => handleChange('representative', e.target.value)}
required
/>
</div>
<div className="data-input-form-bx">
@ -82,6 +109,7 @@ export default function BasicForm() {
id="store"
value={basicInfoData.store ?? ''}
onChange={(e) => handleChange('store', e.target.value)}
required
/>
</div>
<div className="data-input-form-bx">
@ -92,6 +120,7 @@ export default function BasicForm() {
id="construction_point"
value={basicInfoData.construction_point ?? ''}
onChange={(e) => handleChange('construction_point', e.target.value)}
required
/>
</div>
</div>
@ -101,7 +130,6 @@ export default function BasicForm() {
<div className="data-form-wrap">
<div className="data-input-form-bx">
<div className="data-input-form-tit">調</div>
{/* TODO: 달력 라이브러리 추가 ?? */}
<div className="date-input">
<button className="date-btn">
<i className="date-icon"></i>
@ -110,7 +138,7 @@ export default function BasicForm() {
type="date"
className="date-frame"
id="investigation_date"
value={basicInfoData.investigation_date ?? ''}
value={basicInfoData.investigation_date ?? new Date().toISOString().split('T')[0]}
onChange={(e) => handleChange('investigation_date', e.target.value)}
/>
</div>
@ -182,12 +210,12 @@ export default function BasicForm() {
</div>
<div className="btn-flex-wrap">
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleSave}>
<button className="btn-frame n-blue icon" onClick={() => handleSave(true)}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon">
<button className="btn-frame red icon" onClick={() => handleSave(false)}>
<i className="btn-arr"></i>
</button>
</div>

View File

@ -0,0 +1,141 @@
import { SurveyDetailRequest } from '@/types/Survey'
import { useState } from 'react'
const supplementary_facilities = [
{ id: 1, name: 'エコキュート' }, //에코큐트
{ id: 2, name: 'エネパーム' }, //에네팜
{ id: 3, name: '蓄電池システム' }, //축전지시스템
{ id: 4, name: '太陽光発電' }, //태양광발전
]
const roof_material = [
{ id: 1, name: 'スレート' }, //슬레이트
{ id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글
{ id: 3, name: '瓦' }, //기와
{ id: 4, name: '金属屋根' }, //금속지붕
]
export default function MultiCheckbox({
column,
setDetailInfoData,
detailInfoData,
}: {
column: string
setDetailInfoData: (data: any) => void
detailInfoData: SurveyDetailRequest
}) {
const selectList = column === 'supplementary_facilities' ? supplementary_facilities : roof_material
const [isOtherChecked, setIsOtherChecked] = useState(false)
const [otherValue, setOtherValue] = useState('')
const handleCheckbox = (dataIndex: number) => {
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
let newValue: string[]
if (value.includes(String(dataIndex))) {
// 체크 해제
newValue = value.filter((v) => v !== String(dataIndex))
} else {
// 체크
if (column === 'roof_material') {
// 기타가 체크되어 있는지 확인
const isOtherSelected = isOtherChecked
// 현재 선택된 항목 수 + 기타 선택 여부
const totalSelected = value.length + (isOtherSelected ? 1 : 0)
if (totalSelected >= 2) {
alert('屋根材は最大2個まで選択可能です。')
return
}
}
newValue = [...value, String(dataIndex)]
}
setDetailInfoData({
...detailInfoData,
[column]: newValue.join(', '),
})
}
const handleOtherCheckbox = () => {
if (column === 'roof_material') {
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
// 현재 선택된 항목 수
const currentSelected = value.length
if (!isOtherChecked && currentSelected >= 2) {
alert('Up to two roofing materials can be selected.')
return
}
}
setIsOtherChecked(!isOtherChecked)
if (!isOtherChecked) {
setOtherValue('')
setDetailInfoData({
...detailInfoData,
[`${column}_etc`]: null,
})
} else {
setOtherValue('')
}
}
const handleOtherInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setOtherValue(value)
setDetailInfoData({
...detailInfoData,
[`${column}_etc`]: value,
})
}
return (
<>
{column === 'supplementary_facilities' ? (
<>
<div className="data-input-form-tit">
<span></span>
</div>
</>
) : (
<>
<div className="data-input-form-tit">
<span>2</span>
</div>
</>
)}
<div className="data-check-wrap">
{selectList.map((item) => (
<div className="check-form-box" key={item.id}>
<input
type="checkbox"
id={`${column}_ch${item.id}`}
checked={String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.includes(String(item.id))}
onChange={() => handleCheckbox(item.id)}
/>
<label htmlFor={`${column}_ch${item.id}`}>{item.name}</label>
</div>
))}
<div className="check-form-box">
<input type="checkbox" id={`${column}_ch05`} checked={isOtherChecked} onChange={handleOtherCheckbox} />
<label htmlFor={`${column}_ch05`}> ()</label>
</div>
</div>
<div className="data-input">
<input type="text" className="input-frame" disabled={!isOtherChecked} value={otherValue} onChange={handleOtherInputChange} />
</div>
</>
)
}

View File

@ -1,81 +0,0 @@
import { SurveyDetailRequest } from '@/types/Survey'
import { useState } from 'react'
const supplementary_facilities = [
{ id: 1, name: 'エコキュート' },
{ id: 2, name: 'エネパーム' },
{ id: 3, name: '蓄電池システム' },
{ id: 4, name: '太陽光発電' },
]
const roof_material = [
{ id: 1, name: 'スレート' },
{ id: 2, name: 'アスファルトシングル' },
{ id: 3, name: '瓦' },
{ id: 4, name: '金属屋根' },
]
export default function MultiCheckbox({
column,
setDetailInfoData,
detailInfoData,
}: {
column: string
setDetailInfoData: (data: any) => void
detailInfoData: SurveyDetailRequest
}) {
const selectList = column === 'supplementary_facilities' ? supplementary_facilities : roof_material
const [isOtherChecked, setIsOtherChecked] = useState(false)
const handleCheckbox = (dataName: string) => {
const value = column === 'supplementary_facilities' ? detailInfoData.supplementary_facilities : detailInfoData.roof_material
setDetailInfoData({
...detailInfoData,
[column]: `${value}, ${dataName}`,
})
}
return (
<>
{column === 'supplementary_facilities' ? (
<>
<div className="data-input-form-tit">
<span></span>
</div>
</>
) : (
<>
<div className="data-input-form-tit">
<span>2</span>
</div>
</>
)}
<div className="data-check-wrap">
{selectList.map((item) => (
<div className="check-form-box" key={item.id}>
<input
type="checkbox"
id={`ch${item.id}`}
checked={
String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.includes(item.name)
}
onChange={() => handleCheckbox(item.name)}
/>
<label htmlFor={`ch${item.id}`}>{item.name}</label>
</div>
))}
<div className="check-form-box">
<input type="checkbox" id="ch05" checked={isOtherChecked} />
<label htmlFor="ch05"> ()</label>
</div>
</div>
<div className="data-input">
<input type="text" className="input-frame" disabled defaultValue={''} />
</div>
</>
)
}

View File

@ -0,0 +1,128 @@
'use client'
import { useState } from 'react'
import { SurveyDetailRequest } from '@/types/Survey'
type RadioEtcKeys = 'house_structure' | 'rafter_material' | 'waterproof_material' | 'insulation_presence'
const translateJapanese: Record<RadioEtcKeys, string> = {
house_structure: '住宅構造',
rafter_material: '垂木材質',
waterproof_material: '防水材の種類',
insulation_presence: '断熱材の有無',
}
const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = {
house_structure: [
{
id: 1,
label: '木製',
},
],
rafter_material: [
{
id: 1,
label: '木製',
},
{
id: 2,
label: '強制',
},
],
waterproof_material: [
{
id: 1,
label: 'アスファルト屋根94022kg以上',
},
],
insulation_presence: [
{
id: 1,
label: 'なし',
},
{
id: 2,
label: 'あり',
},
],
}
export default function RadioEtc({
column,
setDetailInfoData,
detailInfoData,
}: {
column: RadioEtcKeys
setDetailInfoData: (data: any) => void
detailInfoData: SurveyDetailRequest
}) {
const [isEtcSelected, setIsEtcSelected] = useState(false)
const [etcValue, setEtcValue] = useState('')
const handleRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
if (column === 'insulation_presence') {
setIsEtcSelected(value === '2')
setDetailInfoData({
...detailInfoData,
[column]: Number(value),
})
} else if (value === 'etc') {
setIsEtcSelected(true)
setDetailInfoData({
...detailInfoData,
[column]: null,
})
} else {
setIsEtcSelected(false)
setEtcValue('')
setDetailInfoData({
...detailInfoData,
[column]: Number(value),
[`${column}_etc`]: null,
})
}
}
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setEtcValue(value)
setDetailInfoData({
...detailInfoData,
[`${column}_etc`]: value,
})
}
return (
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f">{translateJapanese[column]}</div>
{radioEtcData[column].map((item) => (
<div className="radio-form-box mb10" key={item.id}>
<input
type="radio"
name={column}
id={`${column}_${item.id}`}
value={item.id}
onChange={handleRadioChange}
checked={detailInfoData[column] === item.id}
/>
<label htmlFor={`${column}_${item.id}`}>{item.label}</label>
</div>
))}
{column !== 'insulation_presence' && (
<div className="radio-form-box mb10">
<input type="radio" name={column} id={`${column}`} value="etc" onChange={handleRadioChange} />
<label htmlFor={`${column}_etc`}> ()</label>
</div>
)}
<div className="data-input">
<input
type="text"
className="input-frame"
disabled={column === 'insulation_presence' ? !isEtcSelected : !isEtcSelected}
value={etcValue}
onChange={handleEtcInputChange}
/>
</div>
</div>
)
}

View File

@ -0,0 +1,321 @@
'use client'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { useServey } from '@/hooks/useSurvey'
import { SurveyDetailRequest } from '@/types/Survey'
import { useRouter, useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import MultiCheckEtc from './MultiCheckEtc'
import SelectBoxEtc from './SelectBoxEtc'
import RadioEtc from './RadioEtc'
const defaultDetailInfoForm: SurveyDetailRequest = {
contract_capacity: null,
retail_company: null,
supplementary_facilities: null,
supplementary_facilities_etc: null,
installation_system: null,
installation_system_etc: null,
construction_year: null,
construction_year_etc: null,
roof_material: null,
roof_material_etc: null,
roof_shape: null,
roof_shape_etc: null,
roof_slope: null,
house_structure: 1,
house_structure_etc: null,
rafter_material: 1,
rafter_material_etc: null,
rafter_size: null,
rafter_size_etc: null,
rafter_pitch: null,
rafter_pitch_etc: null,
rafter_direction: 1,
open_field_plate_kind: null,
open_field_plate_kind_etc: null,
open_field_plate_thickness: null,
leak_trace: false,
waterproof_material: null,
waterproof_material_etc: null,
insulation_presence: 1,
insulation_presence_etc: null,
structure_order: null,
structure_order_etc: null,
installation_availability: null,
installation_availability_etc: null,
memo: null,
}
export default function RoofInfoForm() {
const { setRoofInfoSelected } = useSurveySaleTabState()
useEffect(() => {
setRoofInfoSelected()
}, [])
const router = useRouter()
const searchParams = useSearchParams()
const id = searchParams.get('id')
const { surveyDetail, createSurveyDetail, validateSurveyDetail } = useServey(Number(id))
const [detailInfoData, setDetailInfoData] = useState<SurveyDetailRequest>(defaultDetailInfoForm)
useEffect(() => {
if (surveyDetail?.detail_info) {
const { id, updated_at, created_at, ...rest } = surveyDetail.detail_info
setDetailInfoData(rest)
}
}, [surveyDetail])
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
if (typeof value === 'string') {
const numberValue = value === '' ? null : Number(value)
setDetailInfoData({ ...detailInfoData, [key]: numberValue })
} else {
setDetailInfoData({ ...detailInfoData, [key]: value })
}
}
const handleTextInput = (key: keyof SurveyDetailRequest, value: string) => {
setDetailInfoData({ ...detailInfoData, [key]: value || null })
}
const handleBooleanInput = (key: keyof SurveyDetailRequest, value: boolean) => {
setDetailInfoData({ ...detailInfoData, [key]: value })
}
const handleUnitInput = (value: string) => {
const numericValue = detailInfoData.contract_capacity?.replace(/[^0-9.]/g, '') || ''
setDetailInfoData({
...detailInfoData,
contract_capacity: numericValue ? `${numericValue} ${value}` : value,
})
}
// TODO: 조사매물 저장 요구사항 정립 이후 수정 필요
const handleSave = async () => {
if (id) {
const emptyField = validateSurveyDetail(detailInfoData)
if (emptyField.trim() === '') {
createSurveyDetail({ surveyId: Number(id), surveyDetail: detailInfoData })
router.push(`/survey-sale`)
} else {
alert(emptyField + ' is required')
focusOnInput(emptyField)
}
} else {
alert('save essential information first')
}
}
const focusOnInput = (field: string) => {
const input = document.getElementById(field)
if (input) {
input.focus()
}
}
return (
<>
<div className="sale-frame">
<div className="sale-roof-title"></div>
<div className="data-form-wrap">
<div className="data-input-form-bx">
{/* 전기계약 용량 - contract_capacity */}
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<input
type="text"
className="input-frame"
value={detailInfoData.contract_capacity?.split(' ')[0] ?? ''}
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
/>
</div>
<div className="data-input">
<select
className="select-form"
name="contract_capacity_unit"
id="contract_capacity_unit"
onChange={(e) => handleUnitInput(e.target.value)}
>
<option value="kVA">kVA</option>
<option value="A">A</option>
</select>
</div>
</div>
{/* 전기 소매 회사 - retail_company */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<input
type="text"
className="input-frame"
value={detailInfoData.retail_company ?? ''}
onChange={(e) => handleTextInput('retail_company', e.target.value)}
/>
</div>
{/* 전기 부대 설비 - supplementary_facilities */}
<div className="data-input-form-bx">
<MultiCheckEtc column={'supplementary_facilities'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
</div>
{/* 설치 희망 시스템 - installation_system */}
<SelectBoxEtc column={'installation_system'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
</div>
</div>
<div className="sale-frame">
<div className="sale-roof-title"></div>
<div className="data-form-wrap">
{/* 건축 연수 - construction_year */}
<SelectBoxEtc column={'construction_year'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 지붕재 - roof_material */}
<div className="data-input-form-bx">
<MultiCheckEtc column={'roof_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
</div>
{/* 지붕 모양 - roof_shape */}
<SelectBoxEtc column={'roof_shape'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 지붕 경사도 - roof_slope */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input
type="text"
className="input-frame"
value={detailInfoData.roof_slope ?? ''}
onChange={(e) => handleTextInput('roof_slope', e.target.value)}
/>
<span></span>
</div>
</div>
{/* 주택 구조 - house_structure */}
<RadioEtc column={'house_structure'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 서까래 재질 - rafter_material */}
<RadioEtc column={'rafter_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 서까래 크기 - rafter_size */}
<SelectBoxEtc column={'rafter_size'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 서까래 피치 - rafter_pitch */}
<SelectBoxEtc column={'rafter_pitch'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 서까래 방향 - rafter_direction */}
<div className="data-input-form-bx">
<div className="data-input-form-tit red-f"></div>
<div className="data-check-wrap mb0" id="rafter_direction">
<div className="radio-form-box">
<input
type="radio"
name="rafter_direction"
id="rafter_direction_1"
value={1}
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
checked={detailInfoData.rafter_direction === 1}
/>
<label htmlFor="rafter_direction_1"></label>
</div>
<div className="radio-form-box">
<input
type="radio"
name="rafter_direction"
id="rafter_direction_2"
value={2}
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
checked={detailInfoData.rafter_direction === 2}
/>
<label htmlFor="rafter_direction_2"></label>
</div>
</div>
</div>
{/* 노지판 종류 - open_field_plate_kind */}
<SelectBoxEtc column={'open_field_plate_kind'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 노지판 두께 - open_field_plate_thickness */}
<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={detailInfoData.open_field_plate_thickness ?? ''}
onChange={(e) => handleTextInput('open_field_plate_thickness', e.target.value)}
/>
<span>mm</span>
</div>
</div>
{/* 누수 흔적 - leak_trace */}
<div className="data-input-form-bx">
<div className="data-input-form-tit "></div>
<div className="data-check-wrap mb0">
<div className="radio-form-box">
<input
type="radio"
name="leak_trace"
id="leak_trace_1"
checked={detailInfoData.leak_trace === true}
onChange={(e) => handleBooleanInput('leak_trace', true)}
/>
<label htmlFor="leak_trace_1"></label>
</div>
<div className="radio-form-box">
<input
type="radio"
name="leak_trace"
id="leak_trace_2"
checked={detailInfoData.leak_trace === false}
onChange={(e) => handleBooleanInput('leak_trace', false)}
/>
<label htmlFor="leak_trace_2"></label>
</div>
</div>
</div>
{/* 방수재 종류 - waterproof_material */}
<RadioEtc column={'waterproof_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 단열재 유무 - insulation_presence */}
<RadioEtc column={'insulation_presence'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 노지판 종류 - open_field_plate_kind */}
<SelectBoxEtc column={'structure_order'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 설치 가능 여부 - installation_availability */}
<SelectBoxEtc column={'installation_availability'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
{/* 메모 - memo */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
<div className="data-input">
<textarea
className="textarea-form"
name="memo"
id="memo"
value={detailInfoData.memo ?? ''}
onChange={(e) => handleTextInput('memo', e.target.value)}
placeholder="例: 漏れの兆候があるため、正確な点検が必要です."
></textarea>
</div>
</div>
</div>
<div className="btn-flex-wrap">
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleSave}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSave}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
</div>
</div>
</>
)
}

View File

@ -0,0 +1,234 @@
import type { SurveyDetailRequest } from '@/types/Survey'
import { useEffect, useState } from 'react'
type SelectBoxKeys =
| 'installation_system'
| 'construction_year'
| 'roof_shape'
| 'rafter_pitch'
| 'rafter_size'
| 'open_field_plate_kind'
| 'structure_order'
| 'installation_availability'
const font: Record<SelectBoxKeys, string> = {
installation_system: 'data-input-form-tit red-f',
construction_year: 'data-input-form-tit red-f',
roof_shape: 'data-input-form-tit',
rafter_pitch: 'data-input-form-tit red-f',
rafter_size: 'data-input-form-tit red-f',
open_field_plate_kind: 'data-input-form-tit',
structure_order: 'data-input-form-tit red-f',
installation_availability: 'data-input-form-tit',
}
const translateJapanese: Record<SelectBoxKeys, string> = {
installation_system: '設置希望システム',
construction_year: '建築年数',
roof_shape: '屋根の形状',
rafter_pitch: '垂木傾斜',
rafter_size: '垂木サイズ',
open_field_plate_kind: '路地板の種類',
structure_order: '屋根構造の順序',
installation_availability: '屋根製品名 設置可否確認',
}
const selectBoxOptions: Record<SelectBoxKeys, { id: number; name: string }[]> = {
installation_system: [
{
id: 1,
name: '太陽光発電', //태양광발전
},
{
id: 2,
name: 'ハイブリッド蓄電システム', //하이브리드축전지시스템
},
{
id: 3,
name: '蓄電池システム', //축전지시스템
},
],
construction_year: [
{
id: 1,
name: '新築', //신축
},
{
id: 2,
name: '既築', //기존
},
],
roof_shape: [
{
id: 1,
name: '切妻', //박공지붕
},
{
id: 2,
name: '寄棟', //기동
},
{
id: 3,
name: '片流れ', //한쪽흐름
},
],
rafter_size: [
{
id: 1,
name: '幅35mm以上×高さ48mm以上',
},
{
id: 2,
name: '幅36mm以上×高さ46mm以上',
},
{
id: 3,
name: '幅37mm以上×高さ43mm以上',
},
{
id: 4,
name: '幅38mm以上×高さ40mm以上',
},
],
rafter_pitch: [
{
id: 1,
name: '(455mm以下',
},
{
id: 2,
name: '500mm以下',
},
{
id: 3,
name: '606mm以下',
},
],
open_field_plate_kind: [
{
id: 1,
name: '構造用合板', //구조용합판
},
{
id: 2,
name: 'OSB', //OSB
},
{
id: 3,
name: 'パーティクルボード', //파티클보드
},
{
id: 4,
name: '小幅板', //소판
},
],
structure_order: [
{
id: 1,
name: '屋根材', //지붕재
},
{
id: 2,
name: '防水材', //방수재
},
{
id: 3,
name: '屋根の基礎', //지붕의기초
},
{
id: 4,
name: '垂木', //서까래
},
],
installation_availability: [
{
id: 1,
name: '確認済み', //확인완료
},
{
id: 2,
name: '未確認', //미확인
},
],
}
export default function SelectBoxForm({
column,
setDetailInfoData,
detailInfoData,
}: {
column: SelectBoxKeys
setDetailInfoData: (data: any) => void
detailInfoData: SurveyDetailRequest
}) {
const [isEtcSelected, setIsEtcSelected] = useState(false)
const [etcValue, setEtcValue] = useState('')
useEffect(() => {
setEtcValue(detailInfoData[`${column}_etc`] ?? '')
}, [detailInfoData[`${column}_etc`]])
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value
if (column === 'installation_availability' || column === 'construction_year') {
setIsEtcSelected(value === '2') // 既築(2) 또는 未確認(2) 선택 시 input 활성화
setDetailInfoData({
...detailInfoData,
[column]: Number(value),
})
} else if (value === 'etc') {
setIsEtcSelected(true)
setDetailInfoData({
...detailInfoData,
[column]: null,
})
} else {
setIsEtcSelected(false)
setEtcValue('')
setDetailInfoData({
...detailInfoData,
[`${column}_etc`]: null,
[column]: Number(value),
})
}
}
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setEtcValue(value)
setDetailInfoData({
...detailInfoData,
[`${column}_etc`]: value,
})
}
return (
<>
<div className="data-input-form-bx">
<div className={font[column]}>{translateJapanese[column]}</div>
<div className="data-input mb5">
<select className="select-form" name={column} id={column} onChange={handleSelectChange} value={detailInfoData[column] ?? ''}>
<option value="" hidden>
</option>
{selectBoxOptions[column].map((option) => (
<option key={option.id} value={option.id}>
{option.name}
</option>
))}
{column !== 'installation_availability' && column !== 'construction_year' && <option value="etc"> ()</option>}
</select>
</div>
<div className="data-input">
<input
type="text"
className="input-frame"
value={etcValue ?? ''}
onChange={handleEtcInputChange}
disabled={column === 'installation_availability' || column === 'construction_year' ? !Boolean(detailInfoData[column]) : !isEtcSelected}
/>
</div>
</div>
</>
)
}

View File

@ -15,6 +15,7 @@ export function useServey(id?: number): {
updateSurvey: (survey: SurveyBasicRequest) => void
deleteSurvey: () => Promise<boolean>
submitSurvey: () => void
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
} {
const queryClient = useQueryClient()
@ -97,6 +98,38 @@ export function useServey(id?: number): {
},
})
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
const requiredFields = [
'installation_system',
'construction_year',
'rafter_size',
'rafter_pitch',
'rafter_direction',
'waterproof_material',
'insulation_presence',
'structure_order',
] as const;
const etcFields = [
'installation_system',
'construction_year',
'rafter_size',
'rafter_pitch',
'waterproof_material',
'structure_order',
] as const;
const emptyField = requiredFields.find((field) => {
if (etcFields.includes(field as (typeof etcFields)[number])) {
return surveyDetail[field as keyof SurveyDetailRequest] === null &&
surveyDetail[`${field}_etc` as keyof SurveyDetailRequest] === null;
}
return surveyDetail[field as keyof SurveyDetailRequest] === null;
});
return emptyField || '';
};
return {
surveyList: surveyList || [],
surveyDetail: surveyDetail || null,
@ -110,5 +143,6 @@ export function useServey(id?: number): {
deleteSurvey,
createSurveyDetail,
submitSurvey,
validateSurveyDetail,
}
}

View File

@ -20,13 +20,13 @@ export type SurveyDetailInfo = {
id: number
contract_capacity: string | null
retail_company: string | null
supplementary_facilities: number | null
supplementary_facilities: string | null // number 배열
supplementary_facilities_etc: string | null
installation_system: number | null
installation_system_etc: string | null
construction_year: number | null
construction_year_etc: string | null
roof_material: number | null
roof_material: string | null // number 배열
roof_material_etc: string | null
roof_shape: number | null
roof_shape_etc: string | null
@ -74,13 +74,13 @@ export type SurveyBasicRequest = {
export type SurveyDetailRequest = {
contract_capacity: string | null
retail_company: string | null
supplementary_facilities: number | null
supplementary_facilities: string | null // number 배열
supplementary_facilities_etc: string | null
installation_system: number | null
installation_system_etc: string | null
construction_year: number | null
construction_year_etc: string | null
roof_material: number | null
roof_material: string | null // number 배열
roof_material_etc: string | null
roof_shape: number | null
roof_shape_etc: string | null