diff --git a/src/app/api/survey-sales/[id]/route.ts b/src/app/api/survey-sales/[id]/route.ts index 3da5981..3b88e86 100644 --- a/src/app/api/survey-sales/[id]/route.ts +++ b/src/app/api/survey-sales/[id]/route.ts @@ -1,9 +1,10 @@ -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' +import { convertToSnakeCase } from '../route' -export async function GET(request: Request, context: { params: { id: string } }) { +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { - const { id } = await context.params + const { id } = await params // @ts-ignore const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({ where: { ID: Number(id) }, @@ -18,34 +19,34 @@ export async function GET(request: Request, context: { params: { id: string } }) } } -export async function PUT(request: Request, context: { params: { id: string } }) { +export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { - const { id } = await context.params + const { id } = await params const body = await request.json() - console.log('body:: ', body) - - // DETAIL_INFO를 분리 const { DETAIL_INFO, ...basicInfo } = body + console.log('body:: ', body) // @ts-ignore const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ where: { ID: Number(id) }, data: { - ...basicInfo, + ...convertToSnakeCase(basicInfo), UPT_DT: new Date(), - DETAIL_INFO: DETAIL_INFO - ? { - upsert: { - create: DETAIL_INFO, - update: DETAIL_INFO, - }, + DETAIL_INFO: DETAIL_INFO ? { + upsert: { + create: convertToSnakeCase(DETAIL_INFO), + update: convertToSnakeCase(DETAIL_INFO), + where: { + BASIC_INFO_ID: Number(id) } - : undefined, + } + } : undefined }, include: { - DETAIL_INFO: true, - }, + DETAIL_INFO: true + } }) + console.log('survey:: ', survey) return NextResponse.json(survey) } catch (error) { console.error('Error updating survey:', error) @@ -53,9 +54,9 @@ export async function PUT(request: Request, context: { params: { id: string } }) } } -export async function DELETE(request: Request, context: { params: { id: string } }) { +export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { - const { id } = await context.params + const { id } = await params await prisma.$transaction(async (tx) => { // @ts-ignore @@ -86,9 +87,9 @@ export async function DELETE(request: Request, context: { params: { id: string } } } -export async function PATCH(request: Request, context: { params: { id: string } }) { +export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { - const { id } = await context.params + const { id } = await params const body = await request.json() if (body.submit) { @@ -98,40 +99,42 @@ export async function PATCH(request: Request, context: { params: { id: string } data: { SUBMISSION_STATUS: true, SUBMISSION_DATE: new Date(), + UPT_DT: new Date(), }, }) return NextResponse.json({ message: 'Survey confirmed successfully' }) - } else { - // @ts-ignore - const hasDetails = await prisma.SD_SURVEY_SALES_DETAIL_INFO.findUnique({ - where: { BASIC_INFO_ID: Number(id) }, - }) - - if (hasDetails) { - //@ts-ignore - const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ - where: { ID: Number(id) }, - data: { - UPT_DT: new Date(), - DETAIL_INFO: { - update: body.DETAIL_INFO, - }, - }, - }) - return NextResponse.json(result) - } else { - // @ts-ignore - const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ - where: { ID: Number(id) }, - data: { - DETAIL_INFO: { - create: body.DETAIL_INFO, - }, - }, - }) - return NextResponse.json({ message: 'Survey detail created successfully' }) - } } + // } else { + // // @ts-ignore + // const hasDetails = await prisma.SD_SURVEY_SALES_DETAIL_INFO.findUnique({ + // where: { BASIC_INFO_ID: Number(id) }, + // }) + + // if (hasDetails) { + // //@ts-ignore + // const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ + // where: { ID: Number(id) }, + // data: { + // UPT_DT: new Date(), + // DETAIL_INFO: { + // update: convertToSnakeCase(body.DETAIL_INFO), + // }, + // }, + // }) + // return NextResponse.json(result) + // } else { + // // @ts-ignore + // const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ + // where: { ID: Number(id) }, + // data: { + // DETAIL_INFO: { + // create: convertToSnakeCase(body.DETAIL_INFO), + // }, + // }, + // }) + // return NextResponse.json({ message: 'Survey detail created successfully' }) + // } + // } } catch (error) { console.error('Error updating survey:', error) return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 }) diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index a2df29d..aa9a549 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -219,19 +219,46 @@ export async function PUT(request: Request) { } } -export async function POST(request: Request) { +// 카멜케이스를 스네이크케이스로 변환하는 함수 +export const toSnakeCase = (str: string) => { + return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); +} + +// 객체의 키를 스네이크케이스로 변환하는 함수 +export const convertToSnakeCase = (obj: any): Record => { + if (obj === null || obj === undefined) return obj; + + if (Array.isArray(obj)) { + return obj.map((item: any) => convertToSnakeCase(item)) + } + + if (typeof obj === 'object') { + return Object.keys(obj).reduce((acc, key) => { + const snakeKey = toSnakeCase(key).toUpperCase(); + acc[snakeKey] = convertToSnakeCase(obj[key]); + return acc; + }, {} as Record); + } + + return obj; +} + +export async function POST(request: Request) { try { const body = await request.json() - const { DETAIL_INFO, ...basicInfo } = body + console.log('body:: ', body) + + const { detailInfo, ...basicInfo } = body + // 기본 정보 생성 //@ts-ignore const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ data: { - ...basicInfo, + ...convertToSnakeCase(basicInfo), DETAIL_INFO: { - create: DETAIL_INFO, - }, - }, + create: convertToSnakeCase(detailInfo) + } + } }) return NextResponse.json(result) } catch (error) { diff --git a/src/app/survey-sale/[id]/page.tsx b/src/app/survey-sale/[id]/page.tsx index ac22151..acbb03e 100644 --- a/src/app/survey-sale/[id]/page.tsx +++ b/src/app/survey-sale/[id]/page.tsx @@ -4,7 +4,6 @@ export default function page() { return ( <> - {/* */} ) } diff --git a/src/app/survey-sale/regist/page.tsx b/src/app/survey-sale/regist/page.tsx index 0dec827..251d618 100644 --- a/src/app/survey-sale/regist/page.tsx +++ b/src/app/survey-sale/regist/page.tsx @@ -1,9 +1,9 @@ -import RegistForm from '@/components/survey-sale/detail/RegistForm' +import DetailForm from "@/components/survey-sale/detail/DetailForm"; export default function RegistPage() { return ( <> - + ) } diff --git a/src/components/survey-sale/detail/BasicForm.tsx b/src/components/survey-sale/detail/BasicForm.tsx index 716930e..0942abb 100644 --- a/src/components/survey-sale/detail/BasicForm.tsx +++ b/src/components/survey-sale/detail/BasicForm.tsx @@ -2,18 +2,45 @@ import { useEffect, useState } from 'react' import { useSurveySaleTabState } from '@/store/surveySaleTabState' -import { SurveyBasicRequest } from '@/types/Survey' -import { Mode } from 'fs' +import type { SurveyBasicRequest } from '@/types/Survey' +import type { Mode } from 'fs' +import { useSessionStore } from '@/store/session' +import { usePopupController } from '@/store/popupController' +import { useAddressStore } from '@/store/addressStore' export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBasicInfo: (basicInfo: SurveyBasicRequest) => void; mode: Mode }) { const { basicInfo, setBasicInfo, mode } = props const { setBasicInfoSelected } = useSurveySaleTabState() const [isFlip, setIsFlip] = useState(true) + const { session } = useSessionStore() + const { addressData } = useAddressStore() + useEffect(() => { setBasicInfoSelected() }, []) + useEffect(() => { + if (session?.isLoggedIn) { + setBasicInfo({ + ...basicInfo, + representative: session.userNm ?? '', + store: session.storeNm ?? null, + constructionPoint: session.builderNo ?? null, + }) + } + if (addressData) { + setBasicInfo({ + ...basicInfo, + postCode: addressData.post_code, + address: addressData.address, + addressDetail: addressData.address_detail, + }) + } + }, [session, addressData]) + + const popupController = usePopupController() + return ( <>
@@ -31,31 +58,35 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas setBasicInfo({ ...basicInfo, representative: e.target.value })} />
-
-
販売店
- setBasicInfo({ ...basicInfo, store: e.target.value })} - /> -
-
-
施工店
- setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })} - /> -
+ {(session?.role === 'Builder' || session?.role?.includes('Admin')) && ( +
+
販売店
+ setBasicInfo({ ...basicInfo, store: e.target.value })} + /> +
+ )} + {(session?.role === 'Builder' || session?.role === 'Partner') && ( +
+
施工店
+ setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })} + /> +
+ )}
@@ -67,7 +98,13 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas - + setBasicInfo({ ...basicInfo, investigationDate: e.target.value })} + />
) : ( @@ -76,12 +113,24 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
{/* 건물명 */}
建物名
- + setBasicInfo({ ...basicInfo, buildingName: e.target.value })} + />
{/* 고객명 */} -
建物名
- +
お客様名
+ setBasicInfo({ ...basicInfo, customerName: e.target.value })} + />
郵便番号/都道府県
@@ -92,12 +141,12 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
{/* 도도부현 */}
- +
{/* 주소 */}
-
@@ -105,7 +154,7 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
市区町村名, 以後の住所
- +
diff --git a/src/components/survey-sale/detail/ButtonForm.tsx b/src/components/survey-sale/detail/ButtonForm.tsx index 4b2b9fa..a22ac5c 100644 --- a/src/components/survey-sale/detail/ButtonForm.tsx +++ b/src/components/survey-sale/detail/ButtonForm.tsx @@ -1,81 +1,267 @@ -import { Mode, SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey' +'use client' -export default function ButtonForm(props: { mode: Mode; setMode: (mode: Mode) => void; data: { basic: SurveyBasicRequest; roof: SurveyDetailRequest } }) { +import type { Mode, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' +import { useSessionStore } from '@/store/session' +import { useEffect, useState } from 'react' +import { useParams, useRouter, useSearchParams } from 'next/navigation' +import { requiredFields, useServey } from '@/hooks/useSurvey' + +export default function ButtonForm(props: { + mode: Mode + setMode: (mode: Mode) => void + data: { basic: SurveyBasicRequest; roof: SurveyDetailRequest } +}) { + // 라우터 + const router = useRouter() const { mode, setMode } = props + const { session } = useSessionStore() + + const searchParams = useSearchParams() + const idParam = searchParams.get('id') + + const params = useParams() + const routeId = params.id + + const [isSubmitProcess, setIsSubmitProcess] = useState(false) + // ------------------------------------------------------------ + // 권한 + + // 제출권한 ㅇ + const [isSubmiter, setIsSubmiter] = useState(false) + // 작성자 + const [isWriter, setIsWriter] = useState(false) + const isSubmit = props.data.basic.submissionStatus + + useEffect(() => { + if (session?.isLoggedIn) { + setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPoint) + setIsWriter(session.userNm === props.data.basic.representative) + } + }, [session, props.data]) + + // ------------------------------------------------------------ + // 저장/임시저장/수정 + + const id = routeId ? Number(routeId) : Number(idParam) + const { deleteSurvey, submitSurvey, updateSurvey } = useServey(Number(id)) + const { validateSurveyDetail, createSurvey } = useServey() + let saveData = { + ...props.data.basic, + detailInfo: props.data.roof, + } + + const handleSave = (isTemporary: boolean) => { + const emptyField = validateSurveyDetail(props.data.roof) + console.log('handleSave, emptyField:: ', emptyField) + if (isTemporary) { + tempSaveProcess() + } else { + saveProcess(emptyField) + } + } + + const tempSaveProcess = async () => { + if (idParam) { + await updateSurvey(saveData) + router.push(`/survey-sale/detail?id=${idParam}&isTemporary=true`) + } else { + const id = await createSurvey(saveData) + router.push(`/survey-sale/detail?id=${id}&isTemporary=true`) + } + alert('一時保存されました。') + } + + const focusInput = (field: keyof SurveyDetailInfo) => { + const input = document.getElementById(field) + if (input) { + input.focus() + } + } + + const saveProcess = async (emptyField: string) => { + if (emptyField.trim() === '') { + if (idParam) { + // 수정 페이지에서 작성 후 제출 + if (isSubmitProcess) { + saveData = { + ...saveData, + submissionStatus: true, + submissionDate: new Date().toISOString(), + } + } + await updateSurvey(saveData) + router.push(`/survey-sale/${idParam}`) + } else { + const id = await createSurvey(saveData) + if (isSubmitProcess) { + submitProcess(id) + return + } + router.push(`/survey-sale/${id}`) + } + alert('保存されました。') + } else { + if (emptyField.includes('Unit')) { + alert('電気契約容量の単位を入力してください。') + focusInput(emptyField as keyof SurveyDetailInfo) + } else { + alert(requiredFields.find((field) => field.field === emptyField)?.name + ' 項目が空です。') + focusInput(emptyField as keyof SurveyDetailInfo) + } + } + } + // ------------------------------------------------------------ + // 삭제/제출 + + const handleDelete = async () => { + if (routeId) { + window.neoConfirm('削除しますか?', async () => { + await deleteSurvey() + router.push('/survey-sale') + }) + } + } + + const handleSubmit = async () => { + window.neoConfirm('提出しますか?', async () => { + setIsSubmitProcess(true) + if (routeId) { + submitProcess() + } else { + handleSave(false) + } + }) + } + const submitProcess = async (saveId?: number) => { + await submitSurvey(saveId) + alert('提出されました。') + router.push('/survey-sale') + } + // ------------------------------------------------------------ + + if (mode === 'READ' && isSubmit && isSubmiter) { + return ( + <> +
+
+ +
+
+ + ) + } + return ( <> - {mode === 'CREATE' && ( + {mode === 'READ' && (
-
- {/* 임시저장 */} - -
-
- {/* 저장 */} - -
-
- {/* 목록 */} - -
+ + + {(isWriter || !isSubmiter) && } + {!isSubmit && isSubmiter && }
)} - {mode === 'TEMP' && ( + + {(mode === 'CREATE' || mode === 'EDIT') && (
-
- {/* 수정 */} - -
-
- {/* 삭제 */} - -
-
-
- )} - {mode === 'EDIT' && ( -
-
-
- {/* 목록 */} - -
-
- {/* 제출 */} - -
-
- {/* 수정 */} - -
-
- {/* 삭제 */} - -
+ + + +
)} ) } + +// 목록 버튼 +function ListButton() { + const router = useRouter() + return ( +
+ {/* 목록 */} + +
+ ) +} + +function EditButton(props: { setMode: (mode: Mode) => void; id: string; mode: Mode }) { + const { setMode, id, mode } = props + const router = useRouter() + return ( +
+ {/* 수정 */} + +
+ ) +} + +function SubmitButton(props: { handleSubmit: () => void }) { + const { handleSubmit } = props + return ( +
+ {/* 제출 */} + +
+ ) +} + +function DeleteButton(props: { handleDelete: () => void }) { + const { handleDelete } = props + return ( +
+ {/* 삭제 */} + +
+ ) +} + +function SaveButton(props: { handleSave: (isTemporary: boolean) => void }) { + const { handleSave } = props + return ( +
+ {/* 저장 */} + +
+ ) +} + +function TempButton(props: { setMode: (mode: Mode) => void; handleSave: (isTemporary: boolean) => void }) { + const { setMode, handleSave } = props + const router = useRouter() + + return ( +
+ {/* 임시저장 */} + +
+ ) +} diff --git a/src/components/survey-sale/detail/DataTable.tsx b/src/components/survey-sale/detail/DataTable.tsx index 1d06e2e..210d80d 100644 --- a/src/components/survey-sale/detail/DataTable.tsx +++ b/src/components/survey-sale/detail/DataTable.tsx @@ -4,7 +4,7 @@ import { useServey } from '@/hooks/useSurvey' import { useParams, useSearchParams } from 'next/navigation' import { useEffect, useState } from 'react' import DetailForm from './DetailForm' -import { SurveyBasicInfo } from '@/types/Survey' +import type { SurveyBasicInfo } from '@/types/Survey' export default function DataTable() { const params = useParams() @@ -83,7 +83,7 @@ export default function DataTable() { - + ) } diff --git a/src/components/survey-sale/detail/DetailForm.tsx b/src/components/survey-sale/detail/DetailForm.tsx index 387bb4f..0467aec 100644 --- a/src/components/survey-sale/detail/DetailForm.tsx +++ b/src/components/survey-sale/detail/DetailForm.tsx @@ -1,10 +1,13 @@ 'use client' -import { Mode, SurveyBasicInfo, SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey' +import type { Mode, SurveyBasicInfo, SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey' import { useEffect, useState } from 'react' import ButtonForm from './ButtonForm' import BasicForm from './BasicForm' import RoofForm from './RoofForm' +import { useParams, useSearchParams } from 'next/navigation' +import { useServey } from '@/hooks/useSurvey' + const roofInfoForm: SurveyDetailRequest = { contractCapacity: null, retailCompany: null, @@ -57,21 +60,32 @@ const basicInfoForm: SurveyBasicRequest = { submissionDate: null, } -export default function DetailForm(props: { surveyInfo?: SurveyBasicInfo; mode?: Mode }) { - const [mode, setMode] = useState(props.mode ?? 'CREATE') +export default function DetailForm() { + const idParam = useSearchParams().get('id') + const routeId = useParams().id + + const id = idParam ?? routeId + + const { surveyDetail } = useServey(Number(id)) + + const [mode, setMode] = useState(idParam ? 'EDIT' : routeId ? 'READ' : 'CREATE') const [basicInfoData, setBasicInfoData] = useState(basicInfoForm) const [roofInfoData, setRoofInfoData] = useState(roofInfoForm) useEffect(() => { - if (props.surveyInfo && (mode === 'EDIT' || mode === 'READ')) { - const { id, uptDt, regDt, detailInfo, ...rest } = props.surveyInfo + if (surveyDetail && (mode === 'EDIT' || mode === 'READ')) { + const { id, uptDt, regDt, detailInfo, ...rest } = surveyDetail setBasicInfoData(rest) if (detailInfo) { const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo setRoofInfoData(rest) } } - }, [props.surveyInfo, mode]) + }, [surveyDetail, mode]) + + // console.log('mode:: ', mode) + // console.log('surveyDetail:: ', surveyDetail) + // console.log('roofInfoData:: ', roofInfoData) const data = { basic: basicInfoData, diff --git a/src/components/survey-sale/detail/RegistForm.tsx b/src/components/survey-sale/detail/RegistForm.tsx deleted file mode 100644 index 4377713..0000000 --- a/src/components/survey-sale/detail/RegistForm.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Mode } from '@/types/Survey' -import { useSearchParams } from 'next/navigation' -import DetailForm from './DetailForm' -import { useServey } from '@/hooks/useSurvey' -import { useEffect, useState } from 'react' -import { SurveyBasicInfo } from '@/types/Survey' -import { useSessionStore } from '@/store/session' - -export default function RegistForm() { - const searchParams = useSearchParams() - const id = searchParams.get('id') - - const { surveyDetail } = useServey(Number(id)) - const { session } = useSessionStore() - - const [mode, setMode] = useState('CREATE') - - useEffect(() => { - if (id) { - setMode('EDIT') - } - }, [id]) - - return ( - <> - - - ) -} diff --git a/src/components/survey-sale/detail/RoofForm.tsx b/src/components/survey-sale/detail/RoofForm.tsx index 794bf21..60399fc 100644 --- a/src/components/survey-sale/detail/RoofForm.tsx +++ b/src/components/survey-sale/detail/RoofForm.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' +import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' type RadioEtcKeys = 'houseStructure' | 'rafterMaterial' | 'waterproofMaterial' | 'insulationPresence' | 'rafterDirection' | 'leakTrace' type SelectBoxKeys = @@ -12,14 +12,14 @@ type SelectBoxKeys = | 'structureOrder' | 'installationAvailability' -export const supplementary_facilities = [ +export const supplementaryFacilities = [ { id: 1, name: 'エコキュート' }, //에코큐트 { id: 2, name: 'エネパーム' }, //에네팜 { id: 3, name: '蓄電池システム' }, //축전지시스템 { id: 4, name: '太陽光発電' }, //태양광발전 ] -export const roof_material = [ +export const roofMaterial = [ { id: 1, name: 'スレート' }, //슬레이트 { id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글 { id: 3, name: '瓦' }, //기와 @@ -200,19 +200,47 @@ export const radioEtcData: Record ], } +const makeNumArr = (value: string) => { + return value + .split(',') + .map((v) => v.trim()) + .filter((v) => v.length > 0) +} + export default function RoofForm(props: { roofInfo: SurveyDetailRequest | SurveyDetailInfo setRoofInfo: (roofInfo: SurveyDetailRequest) => void mode: Mode }) { - const makeNumArr = (value: string) => { - return value - .split(',') - .map((v) => v.trim()) - .filter((v) => v.length > 0) - } const { roofInfo, setRoofInfo, mode } = props const [isFlip, setIsFlip] = useState(true) + + const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => { + if (key === 'roofSlope' || key === 'openFieldPlateThickness') { + const stringValue = value.toString() + if (stringValue.length > 5) { + alert('保存できるサイズを超えました。') + return + } + if (stringValue.includes('.')) { + const decimalPlaces = stringValue.split('.')[1].length + if (decimalPlaces > 1) { + alert('小数点以下1桁までしか許されません。') + return + } + } + } + setRoofInfo({ ...roofInfo, [key]: value.toString() }) + } + + const handleUnitInput = (value: string) => { + const numericValue = roofInfo.contractCapacity?.replace(/[^0-9.]/g, '') || '' + setRoofInfo({ + ...roofInfo, + contractCapacity: numericValue ? `${numericValue} ${value}` : value, + }) + } + return (
setIsFlip(!isFlip)}> @@ -229,78 +257,55 @@ export default function RoofForm(props: {
{/* 전기 계약 용량 */}
電気契約容量
-
- -
- {mode === 'READ' && } + {mode === 'READ' && } {mode !== 'READ' && ( -
- +
+ handleNumberInput('contractCapacity', e.target.value)} + /> +
+ +
)}
{/* 전기 소매 회사사 */}
電気小売会社
- + setRoofInfo({ ...roofInfo, retailCompany: e.target.value })} + />
{/* 전기 부대 설비 */}
電気袋設備※複数選択可能
-
- {/*
- - -
*/} - {supplementary_facilities.map((item) => ( -
- - -
- ))} -
- - -
-
-
- -
+
- {/* 설치 희망 시스템 */} - {/*
設置希望システム
- {mode === 'READ' && ( -
- -
- )} - {mode !== 'READ' && ( -
- -
- )} */} -
設置希望システム
- +
設置希望システム
+
@@ -312,108 +317,73 @@ export default function RoofForm(props: { {/* 건축 연수 */}
建築研修
- {/* {mode === 'READ' && } - {mode !== 'READ' && ( - - )} */} - +
- {/*
- - -
*/}
{/* 지붕재 */}
屋根材※最大2個まで選択可能
-
- {roof_material.map((item) => ( -
- - -
- ))} -
- - -
-
-
- -
+
{/* 지붕 모양 */} -
建築研修
+
屋根の形状
- {/* {mode === 'READ' && } - {mode !== 'READ' && ( - - )} */} - -
-
- +
{/* 지붕 경사도도 */}
屋根の斜面
- + handleNumberInput('roofSlope', e.target.value)} + />
{/* 주택구조조 */}
住宅構造
- +
{/* 서까래 재질 */}
垂木材質
- +
{/* 서까래 크기 */}
垂木サイズ
- +
{/* 서까래 피치 */}
垂木サイズ
- +
{/* 서까래 방향 */}
垂木の方向
- +
{/* 노지판 종류류 */}
路地板の種類
- +
@@ -422,7 +392,13 @@ export default function RoofForm(props: { 路地板厚※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載
- + handleNumberInput('openFieldPlateThickness', e.target.value)} + /> mm
@@ -430,28 +406,28 @@ export default function RoofForm(props: { {/* 누수 흔적 */}
水漏れの痕跡
- +
{/* 방수재 종류 */}
防水材の種類
- +
{/* 단열재 유무 */}
断熱材の有無
- +
{/* 지붕 구조의 순서 */}
屋根構造の順序
- +
{/* 지붕 제품명 설치 가능 여부 확인 */}
屋根製品名 設置可否確認
- +
{/* 메모 */} @@ -459,11 +435,12 @@ export default function RoofForm(props: {
@@ -474,23 +451,107 @@ export default function RoofForm(props: { ) } -const SelectedBox = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo }) => { +const SelectedBox = ({ + mode, + column, + detailInfoData, + setRoofInfo, +}: { + mode: Mode + column: string + detailInfoData: SurveyDetailInfo + setRoofInfo: (roofInfo: SurveyDetailRequest) => void +}) => { const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo] const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo] + const [isEtcSelected, setIsEtcSelected] = useState(etcValue !== null && etcValue !== undefined && etcValue !== '') + const [etcVal, setEtcVal] = useState(etcValue?.toString() ?? '') + + const handleSelectChange = (e: React.ChangeEvent) => { + const value = e.target.value + const isSpecialCase = column === 'constructionYear' || column === 'installationAvailability' + const isEtc = value === 'etc' + const isSpecialEtc = isSpecialCase && value === '2' + + const updatedData: typeof detailInfoData = { + ...detailInfoData, + [column]: isEtc ? null : value, + [`${column}Etc`]: isEtc ? '' : null, + } + + if (isSpecialEtc) { + updatedData[column] = value + } + + setIsEtcSelected(isEtc || isSpecialEtc) + if (!isEtc) setEtcVal('') + setRoofInfo(updatedData) + } + + const handleEtcInputChange = (e: React.ChangeEvent) => { + const value = e.target.value + setEtcVal(value) + setRoofInfo({ ...detailInfoData, [`${column}Etc`]: value }) + } + return ( <> - + {selectBoxOptions[column as keyof typeof selectBoxOptions].map((item) => ( + + ))} + {column !== 'installationAvailability' && column !== 'constructionYear' && ( + + )} + - {etcValue && } +
+ +
) } -const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo | null }) => { +const RadioSelected = ({ + mode, + column, + detailInfoData, + setRoofInfo, +}: { + mode: Mode + column: string + detailInfoData: SurveyDetailInfo + setRoofInfo: (roofInfo: SurveyDetailRequest) => void +}) => { let selectedId = detailInfoData?.[column as keyof SurveyDetailInfo] if (column === 'leakTrace') { selectedId = Number(selectedId) @@ -501,28 +562,182 @@ const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoD if (column !== 'rafterDirection') { etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo] } - const etcChecked = etcValue !== null && etcValue !== undefined && etcValue !== '' + const [etcChecked, setEtcChecked] = useState(etcValue !== null && etcValue !== undefined && etcValue !== '') + const [etcVal, setEtcVal] = useState(etcValue?.toString() ?? '') + + const handleRadioChange = (e: React.ChangeEvent) => { + const value = e.target.value + if (column === 'leakTrace') { + handleBooleanRadioChange(value) + } + if (value === 'etc') { + setEtcChecked(true) + setRoofInfo({ ...detailInfoData, [column]: null, [`${column}Etc`]: '' }) + } else { + if (column === 'insulationPresence' && value === '2') { + setEtcChecked(true) + } else { + setEtcChecked(false) + } + setRoofInfo({ ...detailInfoData, [column]: value, [`${column}Etc`]: null }) + } + } + + const handleBooleanRadioChange = (value: string) => { + if (value === '1') { + setRoofInfo({ ...detailInfoData, leakTrace: true }) + } else { + setRoofInfo({ ...detailInfoData, leakTrace: false }) + } + } + + const handleEtcInputChange = (e: React.ChangeEvent) => { + const value = e.target.value + setEtcVal(value) + setRoofInfo({ ...detailInfoData, [`${column}Etc`]: value }) + } - // console.log('column: selectedId', column, selectedId) return ( <> {radioEtcData[column as keyof typeof radioEtcData].map((item) => (
- - + +
))} {column !== 'rafterDirection' && column !== 'leakTrace' && column !== 'insulationPresence' && (
- +
)} - {etcChecked && ( + {column !== 'leakTrace' && column !== 'rafterDirection' && (
- +
)} ) } + +const MultiCheck = ({ + mode, + column, + roofInfo, + setRoofInfo, +}: { + mode: Mode + column: string + roofInfo: SurveyDetailInfo + setRoofInfo: (roofInfo: SurveyDetailRequest) => void +}) => { + const multiCheckData = column === 'supplementaryFacilities' ? supplementaryFacilities : roofMaterial + + const [isOtherCheck, setIsOtherCheck] = useState(false) + const [otherValue, setOtherValue] = useState(roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo]?.toString() ?? '') + + const handleCheckbox = (id: number) => { + const value = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? '')) + const isOtherSelected = roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo] !== null + + let newValue: string[] + if (value.includes(String(id))) { + newValue = value.filter((v) => v !== String(id)) + } else { + if (column === 'roofMaterial') { + const totalSelected = value.length + (isOtherSelected ? 1 : 0) + + if (totalSelected >= 2) { + alert('屋根材は最大2個まで選択できます。') + return + } + } + newValue = [...value, String(id)] + } + setRoofInfo({ ...roofInfo, [column]: newValue.join(',') }) + } + + const handleOtherCheckbox = () => { + if (column === 'roofMaterial') { + const value = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? '')) + const currentSelected = value.length + if (!isOtherCheck && currentSelected >= 2) { + alert('屋根材は最大2個まで選択できます。') + return + } + } + const newIsOtherCheck = !isOtherCheck + setIsOtherCheck(newIsOtherCheck) + setOtherValue('') + + setRoofInfo({ ...roofInfo, [`${column}Etc`]: newIsOtherCheck ? '' : null }) + } + + const handleOtherInputChange = (e: React.ChangeEvent) => { + const value = e.target.value + setOtherValue(value) + setRoofInfo({ ...roofInfo, [`${column}Etc`]: value }) + } + + return ( + <> +
+ {multiCheckData.map((item) => ( +
+ handleCheckbox(item.id)} + /> + +
+ ))} +
+ + +
+
+
+ +
+ + ) +} diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index b1ac703..f1a3847 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation' import SearchForm from './SearchForm' import { useSurveyFilterStore } from '@/store/surveyFilterStore' import { useSessionStore } from '@/store/session' -import { SurveyBasicInfo } from '@/types/Survey' +import type { SurveyBasicInfo } from '@/types/Survey' export default function ListTable() { const router = useRouter() diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 327a85c..7f46e68 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -17,7 +17,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string; } setKeyword(searchKeyword) setSearchOption(option) - // onItemsInit() } const searchOptions = memberRole === 'Partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS @@ -38,7 +37,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string; if (e.target.value === 'all') { setKeyword('') setSearchKeyword('') - // onItemsInit() setSearchOption('all') setOption('all') } else { @@ -80,7 +78,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string; checked={isMySurvey === userId} onChange={() => { setIsMySurvey(isMySurvey === userId ? null : userId) - // onItemsInit() }} /> @@ -94,7 +91,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string; value={sort} onChange={(e) => { setSort(e.target.value as 'created' | 'updated') - // onItemsInit() }} > diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index d07cddf..043cfce 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -7,7 +7,7 @@ import { useSessionStore } from '@/store/session' import { useMemo } from 'react' import { AxiosResponse } from 'axios' -const requiredFields = [ +export const requiredFields = [ { field: 'installationSystem', name: '設置希望システム', @@ -67,7 +67,7 @@ export function useServey(id?: number): { createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void updateSurvey: (survey: SurveyRegistRequest) => void deleteSurvey: () => Promise - submitSurvey: () => void + submitSurvey: (saveId?: number) => void validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string getZipCode: (zipCode: string) => Promise refetchSurveyList: () => void @@ -77,7 +77,7 @@ export function useServey(id?: number): { const { session } = useSessionStore() const { - data, + data: surveyListData, isLoading: isLoadingSurveyList, refetch: refetchSurveyList, } = useQuery({ @@ -100,18 +100,17 @@ export function useServey(id?: number): { enabled: session?.isLoggedIn, }) const surveyData = useMemo(() => { - if (!data) return {} + if (!surveyListData) return { count: 0, data: [] } return { - data: data.data, - count: data.count, + ...surveyListData, } - }, [data]) + }, [surveyListData]) const { data: surveyDetail, isLoading: isLoadingSurveyDetail } = useQuery({ queryKey: ['survey', id], queryFn: async () => { if (id === undefined) throw new Error('id is required') - if (id === null) return null + if (id === null || isNaN(id)) return null const resp = await axiosInstance(null).get(`/api/survey-sales/${id}`) return resp.data }, @@ -132,6 +131,7 @@ export function useServey(id?: number): { const { mutate: updateSurvey, isPending: isUpdatingSurvey } = useMutation({ mutationFn: async (survey: SurveyRegistRequest) => { + console.log('updateSurvey, survey:: ', survey) if (id === undefined) throw new Error('id is required') const resp = await axiosInstance(null).put(`/api/survey-sales/${id}`, survey) return resp.data @@ -166,9 +166,10 @@ export function useServey(id?: number): { }) const { mutateAsync: submitSurvey } = useMutation({ - mutationFn: async () => { - if (id === undefined) throw new Error('id is required') - const resp = await axiosInstance(null).patch(`/api/survey-sales/${id}`, { + mutationFn: async (saveId?: number) => { + const submitId = saveId ?? id + if (!submitId) throw new Error('id is required') + const resp = await axiosInstance(null).patch(`/api/survey-sales/${submitId}`, { submit: true, }) return resp.data @@ -180,12 +181,22 @@ export function useServey(id?: number): { }) const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => { - const etcFields = ['installationSystem', 'constructionYear', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const + const etcFields = [ + 'installationSystem', + 'constructionYear', + 'rafterSize', + 'rafterPitch', + 'waterproofMaterial', + 'structureOrder', + 'insulationPresence', + ] as const const emptyField = requiredFields.find((field) => { if (etcFields.includes(field.field as (typeof etcFields)[number])) { return ( - surveyDetail[field.field as keyof SurveyDetailRequest] === null && surveyDetail[`${field.field}_ETC` as keyof SurveyDetailRequest] === '' + surveyDetail[field.field as keyof SurveyDetailRequest] === null && + (surveyDetail[`${field.field}Etc` as keyof SurveyDetailRequest] === null || + surveyDetail[`${field.field}Etc` as keyof SurveyDetailRequest]?.toString().trim() === '') ) } else { return surveyDetail[field.field as keyof SurveyDetailRequest] === null @@ -197,7 +208,7 @@ export function useServey(id?: number): { return 'contractCapacityUnit' } - return emptyField?.name || '' + return emptyField?.field || '' } const getZipCode = async (zipCode: string): Promise => { @@ -213,7 +224,7 @@ export function useServey(id?: number): { } return { - surveyList: surveyData, + surveyList: surveyData.data, surveyDetail: surveyDetail as SurveyBasicInfo | null, isLoadingSurveyList, isLoadingSurveyDetail, diff --git a/src/libs/axios.ts b/src/libs/axios.ts index d973f9d..6b5fbbc 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -84,10 +84,6 @@ const transformObjectKeys = (obj: any): any => { return obj } -export const camelToSnake = (str: string): string => { - return str.replace(/([A-Z])/g, (group) => `_${group.toLowerCase()}`) -} - const snakeToCamel = (str: string): string => { return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', '')) }