feat: implement create & update survey roof-information with validate
- 조사 매물 지붕 정보 필수값 적용하여 등록 & 수정 가능하도록 구현 - 조사 매물 상세 & 작성 페이지 Nav 탭 기본 정보 미등록 시 라우팅 안되도록 설정정
This commit is contained in:
parent
daf9f31733
commit
fd27bfe7d0
@ -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)
|
||||
//지붕 모양
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -4,7 +4,6 @@ import SearchForm from '@/components/survey-sale/list/SearchForm'
|
||||
export default function page() {
|
||||
return (
|
||||
<>
|
||||
<SearchForm />
|
||||
<ListTable />
|
||||
</>
|
||||
)
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">アスファルト屋根940(22kg以上)</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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
141
src/components/survey-sale/detail/form/MultiCheckEtc.tsx
Normal file
141
src/components/survey-sale/detail/form/MultiCheckEtc.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
128
src/components/survey-sale/detail/form/RadioEtc.tsx
Normal file
128
src/components/survey-sale/detail/form/RadioEtc.tsx
Normal 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: 'アスファルト屋根940(22kg以上)',
|
||||
},
|
||||
],
|
||||
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>
|
||||
)
|
||||
}
|
||||
321
src/components/survey-sale/detail/form/RoofInfoForm.tsx
Normal file
321
src/components/survey-sale/detail/form/RoofInfoForm.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
234
src/components/survey-sale/detail/form/SelectBoxEtc.tsx
Normal file
234
src/components/survey-sale/detail/form/SelectBoxEtc.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user