feat: add survey roof-info validation when Create, Update

- 조사 매물 생성, 수정 시 각 컬럼 필수값 validation 추가
- 조사 매물 수정 페이지에서 기타 옵션 선택 시 값 초기화 되도록 구현
This commit is contained in:
Dayoung 2025-05-08 17:01:10 +09:00
parent dfd5ba419b
commit 2397b7f144
8 changed files with 167 additions and 61 deletions

View File

@ -1,20 +1,20 @@
import { NextResponse } from 'next/server'
export async function POST(request: Request, context: { params: { id: string } }) {
const body = await request.json()
const { id } = await context.params
// export async function POST(request: Request, context: { params: { id: string } }) {
// const body = await request.json()
// const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
detail_info: {
create: body,
},
},
})
return NextResponse.json({ message: 'Survey detail created successfully' })
}
// // @ts-ignore
// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
// where: { id: Number(id) },
// data: {
// detail_info: {
// create: body,
// },
// },
// })
// return NextResponse.json({ message: 'Survey detail created successfully' })
// }
export async function GET(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
@ -80,12 +80,45 @@ export async function DELETE(request: Request, context: { params: { id: string }
export async function PATCH(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
submission_status: true,
},
})
return NextResponse.json({ message: 'Survey confirmed successfully' })
const body = await request.json()
if (body.submit) {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
submission_status: true,
submission_date: new Date(),
},
})
return NextResponse.json({ message: 'Survey confirmed successfully' })
} else {
// @ts-ignore
const hasDetails = await prisma.SD_SERVEY_SALES_DETAIL_INFO.findUnique({
where: { basic_info_id: Number(id) },
})
if (hasDetails) {
//@ts-ignore
const result = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
updated_at: new Date(),
detail_info: {
update: body.detail_info,
},
},
})
return NextResponse.json(result)
} else {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
detail_info: {
create: body.detail_info,
},
},
})
return NextResponse.json({ message: 'Survey detail created successfully' })
}
}
}

View File

@ -30,15 +30,19 @@ export default function DetailForm() {
}
}, [tab, setBasicInfoSelected, setRoofInfoSelected, surveyDetail])
console.log('isTemporary', isTemporary)
console.log('surveyDetail', surveyDetail)
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
}
const handleSubmit = async () => {
if (isTemporary) {
alert('SAVE FIRST')
return
}
if (confirm('submit?')) {
if (surveyDetail?.id) {
// TODO: 제출 페이지 추가
alert('SUBMIT POPUP')
await submitSurvey()
}
}

View File

@ -10,7 +10,7 @@ const defaultBasicInfoForm: SurveyBasicRequest = {
representative: '',
store: null,
construction_point: null,
investigation_date: null,
investigation_date: new Date().toLocaleDateString('en-CA'),
building_name: null,
customer_name: null,
post_code: null,
@ -138,7 +138,7 @@ export default function BasicForm() {
type="date"
className="date-frame"
id="investigation_date"
value={basicInfoData.investigation_date ?? new Date().toISOString().split('T')[0]}
value={basicInfoData.investigation_date ?? ''}
onChange={(e) => handleChange('investigation_date', e.target.value)}
/>
</div>

View File

@ -1,5 +1,5 @@
import { SurveyDetailRequest } from '@/types/Survey'
import { useState } from 'react'
import { useEffect, useState } from 'react'
const supplementary_facilities = [
{ id: 1, name: 'エコキュート' }, //에코큐트
@ -29,11 +29,22 @@ export default function MultiCheckbox({
const [isOtherChecked, setIsOtherChecked] = useState(false)
const [otherValue, setOtherValue] = useState('')
const handleCheckbox = (dataIndex: number) => {
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
const makeNumArr = (value: string) => {
return value
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
}
useEffect(() => {
if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) {
setIsOtherChecked(true)
setOtherValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string)
}
}, [detailInfoData])
const handleCheckbox = (dataIndex: number) => {
const value = makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? ''))
let newValue: string[]
if (value.includes(String(dataIndex))) {
@ -63,11 +74,7 @@ export default function MultiCheckbox({
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 value = makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? ''))
// 현재 선택된 항목 수
const currentSelected = value.length
@ -119,10 +126,7 @@ export default function MultiCheckbox({
<input
type="checkbox"
id={`${column}_ch${item.id}`}
checked={String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.includes(String(item.id))}
checked={makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')).includes(String(item.id))}
onChange={() => handleCheckbox(item.id)}
/>
<label htmlFor={`${column}_ch${item.id}`}>{item.name}</label>

View File

@ -1,5 +1,5 @@
'use client'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { SurveyDetailRequest } from '@/types/Survey'
type RadioEtcKeys = 'house_structure' | 'rafter_material' | 'waterproof_material' | 'insulation_presence'
@ -58,6 +58,13 @@ export default function RadioEtc({
const [isEtcSelected, setIsEtcSelected] = useState(false)
const [etcValue, setEtcValue] = useState('')
useEffect(() => {
if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) {
setIsEtcSelected(true)
setEtcValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string)
}
}, [detailInfoData])
const handleRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
if (column === 'insulation_presence') {
@ -110,7 +117,7 @@ export default function RadioEtc({
))}
{column !== 'insulation_presence' && (
<div className="radio-form-box mb10">
<input type="radio" name={column} id={`${column}`} value="etc" onChange={handleRadioChange} />
<input type="radio" name={column} id={`${column}_etc`} value="etc" onChange={handleRadioChange} checked={isEtcSelected} />
<label htmlFor={`${column}_etc`}> ()</label>
</div>
)}

View File

@ -65,15 +65,26 @@ export default function RoofInfoForm() {
useEffect(() => {
if (surveyDetail?.detail_info) {
const { id, updated_at, created_at, ...rest } = surveyDetail.detail_info
const { id, updated_at, created_at, basic_info_id, ...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 })
if (key === 'roof_slope' || key === 'open_field_plate_thickness') {
const stringValue = value.toString()
if (stringValue.length > 4) {
alert('over db size')
return
}
if (stringValue.includes('.')) {
const decimalPlaces = stringValue.split('.')[1].length
if (decimalPlaces > 1) {
alert('小数点以下1桁までしか許されません。')
return
}
}
setDetailInfoData({ ...detailInfoData, [key]: value.toString() })
} else {
setDetailInfoData({ ...detailInfoData, [key]: value })
}
@ -95,12 +106,22 @@ export default function RoofInfoForm() {
})
}
// TODO: 조사매물 저장 요구사항 정립 이후 수정 필요
const handleSave = async () => {
if (id) {
const emptyField = validateSurveyDetail(detailInfoData)
if (emptyField.trim() === '') {
createSurveyDetail({ surveyId: Number(id), surveyDetail: detailInfoData })
const updatedBasicInfoData = {
detail_info: detailInfoData,
}
try {
createSurveyDetail({
surveyId: Number(id),
surveyDetail: updatedBasicInfoData,
})
} catch (error) {
throw new Error('failed to create survey detail: ' + error)
}
alert('created successfully')
router.push(`/survey-sale`)
} else {
alert(emptyField + ' is required')
@ -126,10 +147,11 @@ export default function RoofInfoForm() {
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<input
type="text"
type="number"
inputMode="decimal"
className="input-frame"
value={detailInfoData.contract_capacity?.split(' ')[0] ?? ''}
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
onChange={(e) => handleNumberInput('contract_capacity', e.target.value)}
/>
</div>
<div className="data-input">
@ -138,7 +160,11 @@ export default function RoofInfoForm() {
name="contract_capacity_unit"
id="contract_capacity_unit"
onChange={(e) => handleUnitInput(e.target.value)}
value={detailInfoData.contract_capacity?.split(' ')[1] ?? ''}
>
<option value="" hidden>
</option>
<option value="kVA">kVA</option>
<option value="A">A</option>
</select>
@ -179,10 +205,12 @@ export default function RoofInfoForm() {
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input
type="text"
type="number"
step={0.1}
inputMode="decimal"
className="input-frame"
value={detailInfoData.roof_slope ?? ''}
onChange={(e) => handleTextInput('roof_slope', e.target.value)}
onChange={(e) => handleNumberInput('roof_slope', e.target.value)}
/>
<span></span>
</div>
@ -205,7 +233,7 @@ export default function RoofInfoForm() {
name="rafter_direction"
id="rafter_direction_1"
value={1}
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
onChange={(e) => handleNumberInput('rafter_direction', Number(e.target.value))}
checked={detailInfoData.rafter_direction === 1}
/>
<label htmlFor="rafter_direction_1"></label>
@ -216,7 +244,7 @@ export default function RoofInfoForm() {
name="rafter_direction"
id="rafter_direction_2"
value={2}
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
onChange={(e) => handleNumberInput('rafter_direction', Number(e.target.value))}
checked={detailInfoData.rafter_direction === 2}
/>
<label htmlFor="rafter_direction_2"></label>
@ -232,10 +260,12 @@ export default function RoofInfoForm() {
</div>
<div className="data-input flex">
<input
type="text"
type="number"
step={0.1}
inputMode="decimal"
className="input-frame"
value={detailInfoData.open_field_plate_thickness ?? ''}
onChange={(e) => handleTextInput('open_field_plate_thickness', e.target.value)}
onChange={(e) => handleNumberInput('open_field_plate_thickness', e.target.value)}
/>
<span>mm</span>
</div>

View File

@ -165,25 +165,28 @@ export default function SelectBoxForm({
const [etcValue, setEtcValue] = useState('')
useEffect(() => {
setEtcValue(detailInfoData[`${column}_etc`] ?? '')
}, [detailInfoData[`${column}_etc`]])
if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) {
setIsEtcSelected(true)
setEtcValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string)
}
}, [detailInfoData])
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value
if (column === 'installation_availability' || column === 'construction_year') {
setIsEtcSelected(value === '2') // 既築(2) 또는 未確認(2) 선택 시 input 활성화
setIsEtcSelected(value === '2') // 건축 연수 & 설치 가능 여부 컬럼 2번 선택 시 input 활성화
setDetailInfoData({
...detailInfoData,
[column]: Number(value),
})
} else if (value === 'etc') {
setIsEtcSelected(true)
setIsEtcSelected(true) // 기타 선택 시 input 활성화
setDetailInfoData({
...detailInfoData,
[column]: null,
})
} else {
setIsEtcSelected(false)
setIsEtcSelected(false) // 기타 선택 해제 시 input 비활성화
setEtcValue('')
setDetailInfoData({
...detailInfoData,
@ -207,7 +210,19 @@ export default function SelectBoxForm({
<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] ?? ''}>
<select
className="select-form"
name={column}
id={column}
onChange={handleSelectChange}
value={
detailInfoData[column]
? detailInfoData[column]
: detailInfoData[`${column}_etc`]
? 'etc'
: ''
}
>
<option value="" hidden>
</option>
@ -225,7 +240,13 @@ export default function SelectBoxForm({
className="input-frame"
value={etcValue ?? ''}
onChange={handleEtcInputChange}
disabled={column === 'installation_availability' || column === 'construction_year' ? !Boolean(detailInfoData[column]) : !isEtcSelected}
disabled={
column === 'installation_availability'
? !Boolean(detailInfoData[column])
: column === 'construction_year'
? detailInfoData[column] !== 2 // 既築(2)가 아닐 때 비활성화
: !isEtcSelected
}
/>
</div>
</div>

View File

@ -1,3 +1,5 @@
import { SEARCH_OPTIONS_ENUM, SEARCH_OPTIONS_PARTNERS_ENUM, SORT_OPTIONS_ENUM } from "@/store/surveyFilterStore"
export type SurveyBasicInfo = {
id: number
representative: string
@ -18,6 +20,7 @@ export type SurveyBasicInfo = {
export type SurveyDetailInfo = {
id: number
basic_info_id: number
contract_capacity: string | null
retail_company: string | null
supplementary_facilities: string | null // number 배열
@ -108,3 +111,7 @@ export type SurveyDetailRequest = {
installation_availability_etc: string | null
memo: string | null
}
export type SurveyDetailCoverRequest = {
detail_info: SurveyDetailRequest
}