From a684a3e5be5ccb2efa656e630ddc35033c4a3334 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Wed, 23 Apr 2025 11:15:10 +0900 Subject: [PATCH 01/33] =?UTF-8?q?feat:=20Inquiry=20sample=20page=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 문의 목록 페이지 - 문의 작성 페이지 --- package.json | 1 + pnpm-lock.yaml | 8 + src/app/inquiry/page.tsx | 9 ++ src/app/inquiry/write/page.tsx | 9 ++ src/components/inquiry/InquiryItems.tsx | 18 +++ src/components/inquiry/InquiryList.tsx | 159 ++++++++++++++++++++ src/components/inquiry/InquirySearch.tsx | 20 +++ src/components/inquiry/InquiryWriteForm.tsx | 66 ++++++++ 8 files changed, 290 insertions(+) create mode 100644 src/app/inquiry/page.tsx create mode 100644 src/app/inquiry/write/page.tsx create mode 100644 src/components/inquiry/InquiryItems.tsx create mode 100644 src/components/inquiry/InquiryList.tsx create mode 100644 src/components/inquiry/InquirySearch.tsx create mode 100644 src/components/inquiry/InquiryWriteForm.tsx diff --git a/package.json b/package.json index 13cd1fe..8b70a72 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@tanstack/react-query-devtools": "^5.71.0", "axios": "^1.8.4", "iron-session": "^8.0.4", + "lucide": "^0.503.0", "mssql": "^11.0.1", "next": "15.2.4", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 920681e..5fbe4ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: iron-session: specifier: ^8.0.4 version: 8.0.4 + lucide: + specifier: ^0.503.0 + version: 0.503.0 mssql: specifier: ^11.0.1 version: 11.0.1 @@ -1100,6 +1103,9 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lucide@0.503.0: + resolution: {integrity: sha512-ZAVlxBU4dbSUAVidb2eT0fH3bTtKCj7M2aZNAVsFOrcnazvYJFu6I8OxFE+Fmx5XNf22Cw4Ln3NBHfBxNfoFOw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2319,6 +2325,8 @@ snapshots: lodash.once@4.1.1: {} + lucide@0.503.0: {} + math-intrinsics@1.1.0: {} micromatch@4.0.8: diff --git a/src/app/inquiry/page.tsx b/src/app/inquiry/page.tsx new file mode 100644 index 0000000..0393249 --- /dev/null +++ b/src/app/inquiry/page.tsx @@ -0,0 +1,9 @@ +import InquiryList from '@/components/inquiry/InquiryList' + +export default function Inquiry() { + return ( +
+ +
+ ) +} diff --git a/src/app/inquiry/write/page.tsx b/src/app/inquiry/write/page.tsx new file mode 100644 index 0000000..2d1a122 --- /dev/null +++ b/src/app/inquiry/write/page.tsx @@ -0,0 +1,9 @@ +import InquiryWriteForm from '@/components/inquiry/InquiryWriteForm' + +export default function InquiryWrite() { + return ( +
+ +
+ ) +} diff --git a/src/components/inquiry/InquiryItems.tsx b/src/components/inquiry/InquiryItems.tsx new file mode 100644 index 0000000..a69e84b --- /dev/null +++ b/src/components/inquiry/InquiryItems.tsx @@ -0,0 +1,18 @@ +'use client' + +export default function InquiryItems({ inquiryData }: { inquiryData: any }) { + return ( +
+ {inquiryData.map((item: any) => ( +
+
{item.title}
+
{item.content}
+
{item.createdAt}
+
{item.writer}
+
{item.category}
+ {item.file &&
{item.file}
} +
+ ))} +
+ ) +} diff --git a/src/components/inquiry/InquiryList.tsx b/src/components/inquiry/InquiryList.tsx new file mode 100644 index 0000000..3a1d14e --- /dev/null +++ b/src/components/inquiry/InquiryList.tsx @@ -0,0 +1,159 @@ +'use client' +import { useState } from 'react' +import InquiryItems from './InquiryItems' +import InquirySearch from './InquirySearch' + +const inquiryDummyData = [ + { + id: 1, + title: 'post', + content: 'content', + file: 'file.png', + createdAt: '2024-01-01', + writer: 'writer', + category: 'A', + }, + { + id: 2, + title: 'post', + content: 'content', + file: 'file.png', + createdAt: '2024-01-01', + writer: 'writer1', + category: 'B', + }, + { + id: 3, + title: 'post', + content: 'content', + file: null, + createdAt: '2024-01-01', + writer: 'writer1', + category: 'C', + }, + { + id: 4, + title: 'post', + content: 'content', + file: null, + createdAt: '2024-01-01', + writer: 'writer1', + category: 'A', + }, + { + id: 5, + title: 'post', + content: 'content', + file: null, + createdAt: '2024-01-01', + writer: 'writer1', + category: 'B', + }, + { + id: 6, + title: 'post', + content: 'content', + file: null, + createdAt: '2024-01-01', + writer: 'writer1', + category: 'C', + }, + { + id: 7, + title: 'post', + content: 'content', + file: 'file.png', + createdAt: '2024-01-01', + writer: 'writer', + category: 'A', + }, + { + id: 8, + title: 'post', + content: 'content', + file: 'file.png', + createdAt: '2024-01-01', + writer: 'writer1', + category: 'B', + }, + { + id: 9, + title: 'post', + content: 'content', + file: null, + createdAt: '2024-01-01', + writer: 'writer1', + category: 'C', + }, + + { + id: 10, + title: 'post', + content: 'content', + file: 'file.png', + createdAt: '2024-01-01', + writer: 'writer1', + category: 'A', + }, + { + id: 11, + title: 'post', + content: 'content', + file: 'file.png', + createdAt: '2024-01-01', + writer: 'writer', + category: 'B', + }, + { + id: 12, + title: 'post', + content: 'content', + file: null, + createdAt: '2024-01-01', + writer: 'writer1', + category: 'C', + }, +] + +export default function InquiryList() { + const inquiryLength = inquiryDummyData.length + const [visibleItems, setVisibleItems] = useState(5) + const [isMyPostsOnly, setIsMyPostsOnly] = useState(false) + + const handleLoadMore = () => { + setVisibleItems((prev) => Math.min(prev + 5, inquiryLength)) + } + + const handleSearch = (e: React.ChangeEvent) => { + console.log(e.target.value) + } + + const filteredData = isMyPostsOnly + ? inquiryDummyData.filter((item) => item.writer === 'writer') + : inquiryDummyData + + return ( +
+ +
+ setIsMyPostsOnly(e.target.checked)} + /> + +
+ + total {inquiryDummyData.length} + + {visibleItems < filteredData.length && } +
+ ) +} diff --git a/src/components/inquiry/InquirySearch.tsx b/src/components/inquiry/InquirySearch.tsx new file mode 100644 index 0000000..b074455 --- /dev/null +++ b/src/components/inquiry/InquirySearch.tsx @@ -0,0 +1,20 @@ +'use client' + +import { Search } from 'lucide-react' +import { useRouter } from 'next/navigation' + +export default function InquirySearch({ handleSearch }: { handleSearch: (e: React.ChangeEvent) => void }) { + const router = useRouter() + return ( +
+

Inquiry Search

+ +
+ + +
+
+ ) +} diff --git a/src/components/inquiry/InquiryWriteForm.tsx b/src/components/inquiry/InquiryWriteForm.tsx new file mode 100644 index 0000000..8d1e0a5 --- /dev/null +++ b/src/components/inquiry/InquiryWriteForm.tsx @@ -0,0 +1,66 @@ +'use client' +import { useState } from 'react' + +export interface InquiryFormData { + category: string + title: string + content: string + file: File[] +} + +export default function InquiryWriteForm() { + const [formData, setFormData] = useState({ + category: 'A', + title: '', + content: '', + file: [], + }) + + const handleFileChange = (e: React.ChangeEvent) => { + const files = Array.from(e.target.files || []) + setFormData({ ...formData, file: files }) + } + + const handleFileDelete = (fileToDelete: File) => { + setFormData({ ...formData, file: formData.file.filter((f) => f !== fileToDelete) }) + } + + return ( +
+
+
+ + +
+
+ + setFormData({ ...formData, title: e.target.value })} /> +
+
+ + -
-
- -
-
- -
-
- -
-
- -
-
- - - ) -} diff --git a/src/components/survey-sale/detail/BasicForm.tsx b/src/components/survey-sale/detail/form/BasicForm.tsx similarity index 82% rename from src/components/survey-sale/detail/BasicForm.tsx rename to src/components/survey-sale/detail/form/BasicForm.tsx index 8eba9e3..c60f2d6 100644 --- a/src/components/survey-sale/detail/BasicForm.tsx +++ b/src/components/survey-sale/detail/form/BasicForm.tsx @@ -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(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
Loading...
} - const { setBasicInfoSelected } = useSurveySaleTabState() - - useEffect(() => { - setBasicInfoSelected() - }, []) - return ( <>
@@ -72,6 +98,7 @@ export default function BasicForm() { id="representative" value={basicInfoData.representative} onChange={(e) => handleChange('representative', e.target.value)} + required />
@@ -82,6 +109,7 @@ export default function BasicForm() { id="store" value={basicInfoData.store ?? ''} onChange={(e) => handleChange('store', e.target.value)} + required />
@@ -92,6 +120,7 @@ export default function BasicForm() { id="construction_point" value={basicInfoData.construction_point ?? ''} onChange={(e) => handleChange('construction_point', e.target.value)} + required />
@@ -101,7 +130,6 @@ export default function BasicForm() {
現地調査日
- {/* TODO: 달력 라이브러리 추가 ?? */}
@@ -182,12 +210,12 @@ export default function BasicForm() {
-
-
diff --git a/src/components/survey-sale/detail/form/MultiCheckEtc.tsx b/src/components/survey-sale/detail/form/MultiCheckEtc.tsx new file mode 100644 index 0000000..d901df0 --- /dev/null +++ b/src/components/survey-sale/detail/form/MultiCheckEtc.tsx @@ -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) => { + const value = e.target.value + setOtherValue(value) + setDetailInfoData({ + ...detailInfoData, + [`${column}_etc`]: value, + }) + } + + return ( + <> + {column === 'supplementary_facilities' ? ( + <> +
+ 電気袋設備※複数選択可能 +
+ + ) : ( + <> +
+ 屋根材※最大2個まで選択可能 +
+ + )} +
+ {selectList.map((item) => ( +
+ v.trim()) + .includes(String(item.id))} + onChange={() => handleCheckbox(item.id)} + /> + +
+ ))} +
+ + +
+
+
+ +
+ + ) +} diff --git a/src/components/survey-sale/detail/form/MultiCheckbox.tsx b/src/components/survey-sale/detail/form/MultiCheckbox.tsx deleted file mode 100644 index 7abfca9..0000000 --- a/src/components/survey-sale/detail/form/MultiCheckbox.tsx +++ /dev/null @@ -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' ? ( - <> -
- 電気袋設備※複数選択可能 -
- - ) : ( - <> -
- 屋根材※最大2個まで選択可能 -
- - )} -
- {selectList.map((item) => ( -
- v.trim()) - .includes(item.name) - } - onChange={() => handleCheckbox(item.name)} - /> - -
- ))} -
- - -
-
-
- -
- - ) -} diff --git a/src/components/survey-sale/detail/form/RadioEtc.tsx b/src/components/survey-sale/detail/form/RadioEtc.tsx new file mode 100644 index 0000000..427995c --- /dev/null +++ b/src/components/survey-sale/detail/form/RadioEtc.tsx @@ -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 = { + house_structure: '住宅構造', + rafter_material: '垂木材質', + waterproof_material: '防水材の種類', + insulation_presence: '断熱材の有無', +} + +const radioEtcData: Record = { + 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) => { + 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) => { + const value = e.target.value + setEtcValue(value) + setDetailInfoData({ + ...detailInfoData, + [`${column}_etc`]: value, + }) + } + + return ( +
+
{translateJapanese[column]}
+ {radioEtcData[column].map((item) => ( +
+ + +
+ ))} + {column !== 'insulation_presence' && ( +
+ + +
+ )} +
+ +
+
+ ) +} diff --git a/src/components/survey-sale/detail/form/RoofInfoForm.tsx b/src/components/survey-sale/detail/form/RoofInfoForm.tsx new file mode 100644 index 0000000..f535af0 --- /dev/null +++ b/src/components/survey-sale/detail/form/RoofInfoForm.tsx @@ -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(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 ( + <> +
+
電気関係
+
+
+ {/* 전기계약 용량 - contract_capacity */} +
電気契約容量
+
+ handleTextInput('contract_capacity', e.target.value)} + /> +
+
+ +
+
+ {/* 전기 소매 회사 - retail_company */} +
+
電気小売会社
+ handleTextInput('retail_company', e.target.value)} + /> +
+ {/* 전기 부대 설비 - supplementary_facilities */} +
+ +
+ {/* 설치 희망 시스템 - installation_system */} + +
+
+ +
+
屋根関係
+
+ {/* 건축 연수 - construction_year */} + + {/* 지붕재 - roof_material */} +
+ +
+ {/* 지붕 모양 - roof_shape */} + + {/* 지붕 경사도 - roof_slope */} +
+
屋根の斜面
+
+ handleTextInput('roof_slope', e.target.value)} + /> + +
+
+ {/* 주택 구조 - house_structure */} + + {/* 서까래 재질 - rafter_material */} + + {/* 서까래 크기 - rafter_size */} + + {/* 서까래 피치 - rafter_pitch */} + + {/* 서까래 방향 - rafter_direction */} +
+
垂木の方向
+
+
+ handleNumberInput('rafter_direction', e.target.value)} + checked={detailInfoData.rafter_direction === 1} + /> + +
+
+ handleNumberInput('rafter_direction', e.target.value)} + checked={detailInfoData.rafter_direction === 2} + /> + +
+
+
+ {/* 노지판 종류 - open_field_plate_kind */} + + {/* 노지판 두께 - open_field_plate_thickness */} +
+
+ 路地板厚※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載 +
+
+ handleTextInput('open_field_plate_thickness', e.target.value)} + /> + mm +
+
+ {/* 누수 흔적 - leak_trace */} +
+
水漏れの痕跡
+
+
+ handleBooleanInput('leak_trace', true)} + /> + +
+
+ handleBooleanInput('leak_trace', false)} + /> + +
+
+
+ {/* 방수재 종류 - waterproof_material */} + + {/* 단열재 유무 - insulation_presence */} + + {/* 노지판 종류 - open_field_plate_kind */} + + {/* 설치 가능 여부 - installation_availability */} + + {/* 메모 - memo */} +
+
メモ
+
+ +
+
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+ + ) +} diff --git a/src/components/survey-sale/detail/form/SelectBoxEtc.tsx b/src/components/survey-sale/detail/form/SelectBoxEtc.tsx new file mode 100644 index 0000000..c813ffb --- /dev/null +++ b/src/components/survey-sale/detail/form/SelectBoxEtc.tsx @@ -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 = { + 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 = { + installation_system: '設置希望システム', + construction_year: '建築年数', + roof_shape: '屋根の形状', + rafter_pitch: '垂木傾斜', + rafter_size: '垂木サイズ', + open_field_plate_kind: '路地板の種類', + structure_order: '屋根構造の順序', + installation_availability: '屋根製品名 設置可否確認', +} + +const selectBoxOptions: Record = { + 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) => { + 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) => { + const value = e.target.value + setEtcValue(value) + setDetailInfoData({ + ...detailInfoData, + [`${column}_etc`]: value, + }) + } + + return ( + <> +
+
{translateJapanese[column]}
+
+ +
+
+ +
+
+ + ) +} diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index 1e56750..00517b7 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -15,6 +15,7 @@ export function useServey(id?: number): { updateSurvey: (survey: SurveyBasicRequest) => void deleteSurvey: () => Promise 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, } } diff --git a/src/types/Survey.ts b/src/types/Survey.ts index 39dcc01..3d08fb5 100644 --- a/src/types/Survey.ts +++ b/src/types/Survey.ts @@ -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 From 115ffb8a749cbe661848feda8d6b922bbf0763f7 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Wed, 7 May 2025 18:06:10 +0900 Subject: [PATCH 13/33] feat: set SurveyList Filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 조사 매물 목록 페이지 필터링 구현 - 기존 survey-sales 샘플 페이지 모두 삭제 --- src/components/survey-sale/list/ListTable.tsx | 47 ++++- .../survey-sale/list/SearchForm.tsx | 6 +- src/components/survey-sales/EtcCheckbox.tsx | 68 ------- src/components/survey-sales/SurveyDetail.tsx | 61 ------- src/components/survey-sales/SurveyFilter.tsx | 22 --- .../survey-sales/SurveySaleList.tsx | 90 ---------- .../write-survey-sales/BasicWriteForm.tsx | 86 --------- .../write-survey-sales/DetailWriteForm.tsx | 157 ---------------- .../write-survey-sales/MainSurveyForm.tsx | 168 ------------------ src/store/surveyFilterStore.ts | 27 +++ 10 files changed, 70 insertions(+), 662 deletions(-) delete mode 100644 src/components/survey-sales/EtcCheckbox.tsx delete mode 100644 src/components/survey-sales/SurveyDetail.tsx delete mode 100644 src/components/survey-sales/SurveyFilter.tsx delete mode 100644 src/components/survey-sales/SurveySaleList.tsx delete mode 100644 src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx delete mode 100644 src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx delete mode 100644 src/components/survey-sales/write-survey-sales/MainSurveyForm.tsx create mode 100644 src/store/surveyFilterStore.ts diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index a9cfd34..c39b00c 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -2,39 +2,72 @@ import LoadMoreButton from '@/components/LoadMoreButton' import { useServey } from '@/hooks/useSurvey' -import { useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' +import SearchForm from './SearchForm' export default function ListTable() { const router = useRouter() const { surveyList, isLoadingSurveyList } = useServey() - const [hasMore, setHasMore] = useState(surveyList.length > 5) const [visibleItems, setVisibleItems] = useState(5) + const [search, setSearch] = useState('') + const [isMyPostsOnly, setIsMyPostsOnly] = useState(false) + const [hasMore, setHasMore] = useState(false) + + // TODO: 로그인 구현 이후 USERNAME 변경 + const username = 'test' + + const filteredSurveyList = useMemo(() => { + let filtered = surveyList + if (search.trim().length > 0) { + filtered = filtered.filter((survey) => survey.building_name?.includes(search)) + } + if (isMyPostsOnly) { + filtered = filtered.filter((survey) => survey.representative === username) + } + return filtered + }, [surveyList, search, isMyPostsOnly, username]) + + useEffect(() => { + setHasMore(filteredSurveyList.length > visibleItems) + }, [filteredSurveyList, visibleItems]) const handleLoadMore = () => { - const newVisibleItems = Math.min(visibleItems + 5, surveyList.length) + const newVisibleItems = Math.min(visibleItems + 5, filteredSurveyList.length) setVisibleItems(newVisibleItems) - setHasMore(newVisibleItems < surveyList.length) + setHasMore(newVisibleItems < filteredSurveyList.length) } const handleScrollToTop = () => { window.scrollTo({ top: 0, behavior: 'smooth' }) } - const handleDetail = (id: number) => { + const handleDetailClick = (id: number) => { router.push(`/survey-sale/${id}`) } + + const handleSearchChange = (e: React.ChangeEvent) => { + setSearch(e.target.value) + setVisibleItems(5) + } + + const handleMyPostsToggle = () => { + setIsMyPostsOnly((prev) => !prev) + setVisibleItems(5) + } + if (isLoadingSurveyList) { return
Loading...
} return ( <> +
    - {surveyList.slice(0, visibleItems).map((survey) => ( -
  • handleDetail(survey.id)}> + {filteredSurveyList.slice(0, visibleItems).map((survey) => ( +
  • handleDetailClick(survey.id)}>
    {survey.id}
    diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 67794a2..8942836 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -2,7 +2,7 @@ import { useRouter } from 'next/navigation' -export default function SearchForm() { +export default function SearchForm({ handleSearch, handleMyPosts }: { handleSearch: (e: React.ChangeEvent) => void, handleMyPosts: () => void }) { const router = useRouter() return (
    @@ -22,13 +22,13 @@ export default function SearchForm() {
    - +
    - +
    diff --git a/src/components/survey-sales/EtcCheckbox.tsx b/src/components/survey-sales/EtcCheckbox.tsx deleted file mode 100644 index 1dd6c03..0000000 --- a/src/components/survey-sales/EtcCheckbox.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client' -import { useState } from "react" -import { SurveyDetailRequest } from "@/types/Survey" - -interface EtcCheckboxProps { - formName: keyof SurveyDetailRequest - label: string - detailInfoForm: SurveyDetailRequest - setDetailInfoForm: (form: SurveyDetailRequest) => void -} - -export default function EtcCheckbox({ formName, label, detailInfoForm, setDetailInfoForm }: EtcCheckboxProps) { - const [showEtcInput, setShowEtcInput] = useState(false) - const etcFieldName = `${formName}_etc` as keyof SurveyDetailRequest - - return ( -
    - -
    -
    - setDetailInfoForm({ - ...detailInfoForm, - [formName]: e.target.checked ? 1 : 0 - })} - /> - -
    - -
    - { - setShowEtcInput(e.target.checked) - if (!e.target.checked) { - setDetailInfoForm({ - ...detailInfoForm, - [etcFieldName]: '' - }) - } - }} - /> - - - {showEtcInput && ( - setDetailInfoForm({ - ...detailInfoForm, - [etcFieldName]: e.target.value - })} - className="border rounded px-2 py-1 ml-2" - /> - )} -
    -
    -
    - ) -} - diff --git a/src/components/survey-sales/SurveyDetail.tsx b/src/components/survey-sales/SurveyDetail.tsx deleted file mode 100644 index bff8618..0000000 --- a/src/components/survey-sales/SurveyDetail.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// 'use client' - -// import { useServey } from '@/hooks/useSurvey' -// import { useParams, useRouter } from 'next/navigation' - -// export default function SurveyDetail() { -// const params = useParams() -// const id = params.id -// const router = useRouter() - -// const { surveyDetail, deleteSurvey, isDeletingSurvey, confirmSurvey } = useServey(Number(id)) - -// console.log('surveyDetail:: ', surveyDetail) - -// const handleDelete = async () => { -// if (confirm('delete?')) { -// if (surveyDetail?.representative) { -// if (surveyDetail.detail_info?.id) { -// await deleteSurvey({ id: Number(surveyDetail.detail_info.id), isDetail: true }) -// } -// await deleteSurvey({ id: Number(id), isDetail: false }) -// } -// alert('delete success') -// router.push('/survey-sales') -// } -// } -// const handleSubmit = () => { -// if (confirm('submit?')) { -// confirmSurvey(Number(id)) -// } -// alert('submit success') -// router.push('/survey-sales') -// } - -// if (isDeletingSurvey) { -// return
    Deleting...
    -// } - -// return ( -//
    -//

    SurveyDetail

    -//

    {id}

    -//

    {surveyDetail?.representative}

    -//
    -// -// -// -// -//
    -// -//
    -// ) -// } diff --git a/src/components/survey-sales/SurveyFilter.tsx b/src/components/survey-sales/SurveyFilter.tsx deleted file mode 100644 index 2249186..0000000 --- a/src/components/survey-sales/SurveyFilter.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Search } from 'lucide-react' -import { useRouter } from 'next/navigation' - -export default function SurveyFilter({ handleSearch, handleMyPosts }: { handleSearch: (e: React.ChangeEvent) => void, handleMyPosts: () => void }) { - const router = useRouter() - return ( -
    - -
    - - -
    -
    - -
    -
    - ) -} diff --git a/src/components/survey-sales/SurveySaleList.tsx b/src/components/survey-sales/SurveySaleList.tsx deleted file mode 100644 index 92b9ece..0000000 --- a/src/components/survey-sales/SurveySaleList.tsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client' - -import { useServey } from '@/hooks/useSurvey' -import LoadMoreButton from '@/components/LoadMoreButton' -import { useState } from 'react' -import SurveyFilter from './SurveyFilter' -import { useRouter } from 'next/navigation' - -export default function SurveySaleList() { - const { surveyList, isLoadingSurveyList } = useServey() - const [search, setSearch] = useState('') - const [isMyPostsOnly, setIsMyPostsOnly] = useState(false) - const [hasMore, setHasMore] = useState(surveyList.length > 5) - const [visibleItems, setVisibleItems] = useState(5) - - const router = useRouter() - - // TEMP USERNAME - const username = 'test' - - const surveyData = () => { - if (search.trim().length > 0) { - return surveyList.filter((survey) => survey.building_name?.includes(search)) - } - if (isMyPostsOnly) { - return surveyList.filter((survey) => survey.representative === username) - } - return surveyList - } - - const handleLoadMore = () => { - const newVisibleItems = Math.min(visibleItems + 5, surveyData().length) - setVisibleItems(newVisibleItems) - setHasMore(newVisibleItems < surveyData().length) - } - - const handleScrollToTop = () => { - window.scrollTo({ top: 0, behavior: 'smooth' }) - } - - const handleSearch = (e: React.ChangeEvent) => { - setSearch(e.target.value) - } - - const handleDetail = (id: number | undefined) => { - if (id === undefined) throw new Error('id is required') - router.push(`/survey-sales/${id}`) - } - - if (isLoadingSurveyList) { - return
    Loading...
    - } - - return ( -
    - setIsMyPostsOnly(!isMyPostsOnly)} /> - -
    - {surveyData().slice(0, visibleItems).map((survey) => ( -
    handleDetail(survey.id)} - className="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow cursor-pointer border border-gray-200" - > -
    -
    -

    {survey.id}

    -
    -

    담당자: {survey.representative || '-'}

    -

    판매점: {survey.store || '-'}

    -
    -
    - - {survey.submission_status ? '제출' : '미제출'} - -
    -
    - ))} -
    - -
    - -
    -
    - ) -} diff --git a/src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx b/src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx deleted file mode 100644 index 8fe0a6f..0000000 --- a/src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx +++ /dev/null @@ -1,86 +0,0 @@ -'use client' - -import { SurveyBasicRequest } from '@/types/Survey' - -export default function BasicWriteForm({ - basicInfoData, - setBasicInfoData, -}: { - basicInfoData: SurveyBasicRequest - setBasicInfoData: (basicInfoData: SurveyBasicRequest) => void -}) { - const handleChange = (key: keyof SurveyBasicRequest, value: string) => { - setBasicInfoData({ ...basicInfoData, [key]: value.toString() }) - } - - return ( -
    -
    - - handleChange('representative', e.target.value)} - /> -
    -
    - - handleChange('store', e.target.value)} /> -
    -
    - - handleChange('construction_point', e.target.value)} - /> -
    -
    - - handleChange('investigation_date', e.target.value)} - /> -
    -
    - - handleChange('building_name', e.target.value)} - /> -
    -
    - - handleChange('customer_name', e.target.value)} - /> -
    -
    - - handleChange('post_code', e.target.value)} /> -
    -
    - - handleChange('address', e.target.value)} /> -
    -
    - - handleChange('address_detail', e.target.value)} - /> -
    -
    - ) -} diff --git a/src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx b/src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx deleted file mode 100644 index 032d6f1..0000000 --- a/src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx +++ /dev/null @@ -1,157 +0,0 @@ -'use client' -import React from 'react' -import EtcCheckbox from '../EtcCheckbox' -import { SurveyDetailRequest } from '@/types/Survey' - -interface DetailWriteFormProps { - detailInfoForm: SurveyDetailRequest - setDetailInfoForm: (form: SurveyDetailRequest) => void -} - -export default function DetailWriteForm({ detailInfoForm, setDetailInfoForm }: DetailWriteFormProps) { - const handleNumberInput = (field: keyof SurveyDetailRequest, value: string) => { - const numberValue = value === '' ? null : Number(value) - setDetailInfoForm({ ...detailInfoForm, [field]: numberValue }) - } - - const handleTextInput = (field: keyof SurveyDetailRequest, value: string) => { - setDetailInfoForm({ ...detailInfoForm, [field]: value || null }) - } - - const handleBooleanInput = (field: keyof SurveyDetailRequest, checked: boolean) => { - setDetailInfoForm({ ...detailInfoForm, [field]: checked }) - } - - return ( -
    -
    - - handleTextInput('contract_capacity', e.target.value)} - /> -
    - -
    - - handleTextInput('retail_company', e.target.value)} - /> -
    - - - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - - handleTextInput('roof_slope', e.target.value)} - /> -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - - handleTextInput('rafter_direction', e.target.value)} - /> -
    - -
    - -
    - -
    - - handleTextInput('open_field_plate_thickness', e.target.value)} - /> -
    - -
    - - handleBooleanInput('leak_trace', e.target.checked)} - /> -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    diff --git a/src/components/survey-sale/detail/form/SelectBoxEtc.tsx b/src/components/survey-sale/detail/form/SelectBoxEtc.tsx index d1f1950..9469a40 100644 --- a/src/components/survey-sale/detail/form/SelectBoxEtc.tsx +++ b/src/components/survey-sale/detail/form/SelectBoxEtc.tsx @@ -2,39 +2,39 @@ import type { SurveyDetailRequest } from '@/types/Survey' import { useEffect, useState } from 'react' export type SelectBoxKeys = - | 'installation_system' - | 'construction_year' - | 'roof_shape' - | 'rafter_pitch' - | 'rafter_size' - | 'open_field_plate_kind' - | 'structure_order' - | 'installation_availability' + | 'INSTALLATION_SYSTEM' + | 'CONSTRUCTION_YEAR' + | 'ROOF_SHAPE' + | 'RAFTER_PITCH' + | 'RAFTER_SIZE' + | 'OPEN_FIELD_PLATE_KIND' + | 'STRUCTURE_ORDER' + | 'INSTALLATION_AVAILABILITY' const font: Record = { - 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', + 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 = { - installation_system: '設置希望システム', - construction_year: '建築年数', - roof_shape: '屋根の形状', - rafter_pitch: '垂木傾斜', - rafter_size: '垂木サイズ', - open_field_plate_kind: '路地板の種類', - structure_order: '屋根構造の順序', - installation_availability: '屋根製品名 設置可否確認', + INSTALLATION_SYSTEM: '設置希望システム', + CONSTRUCTION_YEAR: '建築年数', + ROOF_SHAPE: '屋根の形状', + RAFTER_PITCH: '垂木傾斜', + RAFTER_SIZE: '垂木サイズ', + OPEN_FIELD_PLATE_KIND: '路地板の種類', + STRUCTURE_ORDER: '屋根構造の順序', + INSTALLATION_AVAILABILITY: '屋根製品名 設置可否確認', } export const selectBoxOptions: Record = { - installation_system: [ + INSTALLATION_SYSTEM: [ { id: 1, name: '太陽光発電', //태양광발전 @@ -48,7 +48,7 @@ export const selectBoxOptions: Record { - if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) { + if (detailInfoData[`${column}_ETC` as keyof SurveyDetailRequest]) { setIsEtcSelected(true) - setEtcValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string) + setEtcValue(detailInfoData[`${column}_ETC` as keyof SurveyDetailRequest] as string) } }, [detailInfoData]) const handleSelectChange = (e: React.ChangeEvent) => { const value = e.target.value - if (column === 'installation_availability' || column === 'construction_year') { - setIsEtcSelected(value === '2') // 건축 연수 & 설치 가능 여부 컬럼 2번 선택 시 input 활성화 - setDetailInfoData({ - ...detailInfoData, - [column]: Number(value), - }) - } else if (value === 'etc') { - setIsEtcSelected(true) // 기타 선택 시 input 활성화 - setDetailInfoData({ - ...detailInfoData, - [column]: null, - }) - } else { - setIsEtcSelected(false) // 기타 선택 해제 시 input 비활성화 - setEtcValue('') - setDetailInfoData({ - ...detailInfoData, - [`${column}_etc`]: null, - [column]: Number(value), - }) + const isSpecialCase = column === 'CONSTRUCTION_YEAR' || column === 'INSTALLATION_AVAILABILITY' + const isEtc = value === 'etc' + const isSpecialEtc = isSpecialCase && value === '2' + + const updatedData: typeof detailInfoData = { + ...detailInfoData, + [column]: isEtc ? null : value, + [`${column}_ETC`]: isEtc ? '' : null, } + + // 건축연수 + 설치가능여부는 2번 선택 시 input 활성화 + if (isSpecialEtc) { + updatedData[column] = value + } + + setIsEtcSelected(isEtc || isSpecialEtc) + if (!isEtc) setEtcValue('') + setDetailInfoData(updatedData) } const handleEtcInputChange = (e: React.ChangeEvent) => { @@ -201,31 +198,31 @@ export default function SelectBoxForm({ setEtcValue(value) setDetailInfoData({ ...detailInfoData, - [`${column}_etc`]: value, + [`${column}_ETC`]: value, }) } return ( <>
    -
    {translateJapanese[column]}
    +
    {translateJapanese[column as keyof typeof translateJapanese]}
    @@ -235,10 +232,10 @@ export default function SelectBoxForm({ value={etcValue ?? ''} onChange={handleEtcInputChange} disabled={ - column === 'installation_availability' + column === 'INSTALLATION_AVAILABILITY' ? !Boolean(detailInfoData[column]) - : column === 'construction_year' - ? detailInfoData[column] !== 2 // 既築(2)가 아닐 때 비활성화 + : column === 'CONSTRUCTION_YEAR' + ? detailInfoData[column] !== '2' // 既築(2)가 아닐 때 비활성화 : !isEtcSelected } /> diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index ade76f2..63eb41b 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -50,17 +50,17 @@ export default function ListTable() {
      {heldSurveyList.map((survey) => ( -
    • handleDetailClick(survey.id)}> +
    • handleDetailClick(survey.ID)}>
      -
      {survey.id}
      -
      {survey.investigation_date}
      +
      {survey.ID}
      +
      {survey.INVESTIGATION_DATE}
      -
      {survey.building_name}
      -
      {survey.customer_name}
      +
      {survey.BUILDING_NAME}
      +
      {survey.CUSTOMER_NAME}
      -
      {survey.representative}
      -
      {new Date(survey.updated_at).toLocaleString()}
      +
      {survey.REPRESENTATIVE}
      +
      {new Date(survey.UPT_DT).toLocaleString()}
    • diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index 9ab9b47..ea04db4 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -77,7 +77,7 @@ export function useServey(id?: number): { const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ mutationFn: async (survey: SurveyBasicRequest) => { const resp = await axiosInstance(null).post('/api/survey-sales', survey) - return resp.data.id ?? 0 + return resp.data.ID ?? 0 }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['survey', 'list'] }) @@ -138,28 +138,29 @@ 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', + '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 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 && surveyDetail[`${field}_ETC` as keyof SurveyDetailRequest] === null + } else { + return surveyDetail[field as keyof SurveyDetailRequest] === null } - return surveyDetail[field as keyof SurveyDetailRequest] === null }) - const contractCapacity = surveyDetail.contract_capacity + const contractCapacity = surveyDetail.CONTRACT_CAPACITY if (contractCapacity && contractCapacity.trim() !== '' && contractCapacity.split(' ')?.length === 1) { - return 'contract_capacity_unit' + return 'CONTRACT_CAPACITY_UNIT' } return emptyField || '' diff --git a/src/types/Survey.ts b/src/types/Survey.ts index 1c68c42..7a3be3c 100644 --- a/src/types/Survey.ts +++ b/src/types/Survey.ts @@ -1,117 +1,117 @@ import { SEARCH_OPTIONS_ENUM, SEARCH_OPTIONS_PARTNERS_ENUM, SORT_OPTIONS_ENUM } from '@/store/surveyFilterStore' export type SurveyBasicInfo = { - id: number - representative: string - store: string | null - construction_point: string | null - investigation_date: string | null - building_name: string | null - customer_name: string | null - post_code: string | null - address: string | null - address_detail: string | null - submission_status: boolean - submission_date: string | null - detail_info: SurveyDetailInfo | null - created_at: Date - updated_at: Date + ID: number + REPRESENTATIVE: string + STORE: string | null + CONSTRUCTION_POINT: string | null + INVESTIGATION_DATE: string | null + BUILDING_NAME: string | null + CUSTOMER_NAME: string | null + POST_CODE: string | null + ADDRESS: string | null + ADDRESS_DETAIL: string | null + SUBMISSION_STATUS: boolean + SUBMISSION_DATE: string | null + DETAIL_INFO: SurveyDetailInfo | null + REG_DT: Date + UPT_DT: Date } export type SurveyDetailInfo = { - id: number - basic_info_id: number - contract_capacity: string | null - retail_company: string | 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: string | null // number 배열 - roof_material_etc: string | null - roof_shape: number | null - roof_shape_etc: string | null - roof_slope: string | null - house_structure: number | null - house_structure_etc: string | null - rafter_material: number | null - rafter_material_etc: string | null - rafter_size: number | null - rafter_size_etc: string | null - rafter_pitch: number | null - rafter_pitch_etc: string | null - rafter_direction: number | null - open_field_plate_kind: number | null - open_field_plate_kind_etc: string | null - open_field_plate_thickness: string | null - leak_trace: boolean | null - waterproof_material: number | null - waterproof_material_etc: string | null - insulation_presence: number | null - insulation_presence_etc: string | null - structure_order: number | null - structure_order_etc: string | null - installation_availability: number | null - installation_availability_etc: string | null - memo: string | null - created_at: Date - updated_at: Date + ID: number + BASIC_INFO_ID: number + CONTRACT_CAPACITY: string | null + RETAIL_COMPANY: string | null + SUPPLEMENTARY_FACILITIES: string | null // number 배열 + SUPPLEMENTARY_FACILITIES_ETC: string | null + INSTALLATION_SYSTEM: string | null + INSTALLATION_SYSTEM_ETC: string | null + CONSTRUCTION_YEAR: string | null + CONSTRUCTION_YEAR_ETC: string | null + ROOF_MATERIAL: string | null // number 배열 + ROOF_MATERIAL_ETC: string | null + ROOF_SHAPE: string | null + ROOF_SHAPE_ETC: string | null + ROOF_SLOPE: string | null + HOUSE_STRUCTURE: string | null + HOUSE_STRUCTURE_ETC: string | null + RAFTER_MATERIAL: string | null + RAFTER_MATERIAL_ETC: string | null + RAFTER_SIZE: string | null + RAFTER_SIZE_ETC: string | null + RAFTER_PITCH: string | null + RAFTER_PITCH_ETC: string | null + RAFTER_DIRECTION: string | null + OPEN_FIELD_PLATE_KIND: string | null + OPEN_FIELD_PLATE_KIND_ETC: string | null + OPEN_FIELD_PLATE_THICKNESS: string | null + LEAK_TRACE: boolean | null + WATERPROOF_MATERIAL: string | null + WATERPROOF_MATERIAL_ETC: string | null + INSULATION_PRESENCE: string | null + INSULATION_PRESENCE_ETC: string | null + STRUCTURE_ORDER: string | null + STRUCTURE_ORDER_ETC: string | null + INSTALLATION_AVAILABILITY: string | null + INSTALLATION_AVAILABILITY_ETC: string | null + MEMO: string | null + REG_DT: Date + UPT_DT: Date } export type SurveyBasicRequest = { - representative: string - store: string | null - construction_point: string | null - investigation_date: string | null - building_name: string | null - customer_name: string | null - post_code: string | null - address: string | null - address_detail: string | null - submission_status: boolean - submission_date: string | null + REPRESENTATIVE: string + STORE: string | null + CONSTRUCTION_POINT: string | null + INVESTIGATION_DATE: string | null + BUILDING_NAME: string | null + CUSTOMER_NAME: string | null + POST_CODE: string | null + ADDRESS: string | null + ADDRESS_DETAIL: string | null + SUBMISSION_STATUS: boolean + SUBMISSION_DATE: string | null } export type SurveyDetailRequest = { - contract_capacity: string | null - retail_company: string | 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: string | null // number 배열 - roof_material_etc: string | null - roof_shape: number | null - roof_shape_etc: string | null - roof_slope: string | null - house_structure: number | null - house_structure_etc: string | null - rafter_material: number | null - rafter_material_etc: string | null - rafter_size: number | null - rafter_size_etc: string | null - rafter_pitch: number | null - rafter_pitch_etc: string | null - rafter_direction: number | null - open_field_plate_kind: number | null - open_field_plate_kind_etc: string | null - open_field_plate_thickness: string | null - leak_trace: boolean | null - waterproof_material: number | null - waterproof_material_etc: string | null - insulation_presence: number | null - insulation_presence_etc: string | null - structure_order: number | null - structure_order_etc: string | null - installation_availability: number | null - installation_availability_etc: string | null - memo: string | null + CONTRACT_CAPACITY: string | null + RETAIL_COMPANY: string | null + SUPPLEMENTARY_FACILITIES: string | null // number 배열 + SUPPLEMENTARY_FACILITIES_ETC: string | null + INSTALLATION_SYSTEM: string | null + INSTALLATION_SYSTEM_ETC: string | null + CONSTRUCTION_YEAR: string | null + CONSTRUCTION_YEAR_ETC: string | null + ROOF_MATERIAL: string | null // number 배열 + ROOF_MATERIAL_ETC: string | null + ROOF_SHAPE: string | null + ROOF_SHAPE_ETC: string | null + ROOF_SLOPE: string | null + HOUSE_STRUCTURE: string | null + HOUSE_STRUCTURE_ETC: string | null + RAFTER_MATERIAL: string | null + RAFTER_MATERIAL_ETC: string | null + RAFTER_SIZE: string | null + RAFTER_SIZE_ETC: string | null + RAFTER_PITCH: string | null + RAFTER_PITCH_ETC: string | null + RAFTER_DIRECTION: string | null + OPEN_FIELD_PLATE_KIND: string | null + OPEN_FIELD_PLATE_KIND_ETC: string | null + OPEN_FIELD_PLATE_THICKNESS: string | null + LEAK_TRACE: boolean | null + WATERPROOF_MATERIAL: string | null + WATERPROOF_MATERIAL_ETC: string | null + INSULATION_PRESENCE: string | null + INSULATION_PRESENCE_ETC: string | null + STRUCTURE_ORDER: string | null + STRUCTURE_ORDER_ETC: string | null + INSTALLATION_AVAILABILITY: string | null + INSTALLATION_AVAILABILITY_ETC: string | null + MEMO: string | null } export type SurveyDetailCoverRequest = { - detail_info: SurveyDetailRequest + DETAIL_INFO: SurveyDetailRequest } From 47be6a4433c9f237c9b8f0255a6f3e48d548da3f Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Wed, 14 May 2025 09:59:12 +0900 Subject: [PATCH 26/33] feat: add conditions for read survey list on member type --- src/app/api/survey-sales/route.ts | 264 ++++++++++++------ .../survey-sale/detail/DetailButton.tsx | 12 +- .../survey-sale/detail/form/BasicForm.tsx | 16 +- src/components/survey-sale/list/ListTable.tsx | 14 +- .../survey-sale/list/SearchForm.tsx | 6 +- src/hooks/useSurvey.ts | 6 +- src/hooks/useUserType.ts | 21 +- 7 files changed, 228 insertions(+), 111 deletions(-) diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index ce6ce6f..1343880 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -1,105 +1,211 @@ import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' +// Types +type SearchParams = { + keyword?: string | null + searchOption?: string | null + isMySurvey?: string | null + sort?: string | null + offset?: string | null + memberRole?: string | null + store?: string | null + builderNo?: string | null +} + +type WhereCondition = { + AND?: any[] + OR?: any[] + [key: string]: any +} + +// Constants +const SEARCH_OPTIONS = [ + 'BUILDING_NAME', + 'REPRESENTATIVE', + 'STORE', + 'CONSTRUCTION_POINT', + 'CUSTOMER_NAME', + 'POST_CODE', + 'ADDRESS', + 'ADDRESS_DETAIL', +] as const + +const ITEMS_PER_PAGE = 10 + +// Helper functions +const createKeywordSearchCondition = (keyword: string, searchOption: string): WhereCondition => { + const where: WhereCondition = {} + + if (searchOption === 'all') { + where.OR = [] + + // ID 검색 조건 추가 + if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) { + where.OR.push({ + ID: { equals: Number(keyword) }, + }) + } + + // 다른 필드 검색 조건 추가 + where.OR.push( + ...SEARCH_OPTIONS.map((field) => ({ + [field]: { contains: keyword }, + })), + ) + } else if (SEARCH_OPTIONS.includes(searchOption as any)) { + where[searchOption] = { contains: keyword } + } else if (searchOption === 'id') { + where.ID = { equals: Number(keyword) } + } + + return where +} + +const createMemberRoleCondition = (params: SearchParams): WhereCondition => { + const where: WhereCondition = { AND: [] } + + switch (params.memberRole) { + // 1차점: 같은 판매점에서 작성된 매물 + 2차점에서 제출받은 매물 + case 'Admin': + where.OR = [ + { + AND: [ + { STORE: { equals: params.store } }, + { SUBMISSION_STATUS: { equals: false } } + ] + }, + { + AND: [ + { STORE: { endsWith: params.store } }, + { SUBMISSION_STATUS: { equals: true } } + ] + } + ] + break + + // 2차점: 같은 판매점에서 작성된 매물 + Builder에게 제출받은 매물 + case 'Admin_Sub': + where.OR = [ + { + AND: [ + { STORE: { equals: params.store } }, + { CONSTRUCTION_POINT: { equals: null } }, + { SUBMISSION_STATUS: { equals: false } } + ] + }, + { + AND: [ + { STORE: { equals: params.store } }, + { CONSTRUCTION_POINT: { not: null } }, + { SUBMISSION_STATUS: { equals: true } } + ] + } + ] + break + + // 2차점 시공권한: 같은 시공ID에서 작성된 매물 + case 'Builder': + where.AND?.push({ + CONSTRUCTION_POINT: { equals: params.builderNo }, + SUBMISSION_STATUS: { equals: false }, + }) + break + + // 시공점: 같은 시공ID에서 작성된 매물 + case 'Partner': + where.AND?.push({ + CONSTRUCTION_POINT: { equals: params.builderNo }, + SUBMISSION_STATUS: { equals: false }, + }) + break + + // 모든 매물 조회 가능 + case 'T01': + case 'User': + break + } + + return where +} + +// API Routes export async function POST(request: Request) { - const body = await request.json() - // @ts-ignore - const res = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ - data: body, - }) - return NextResponse.json(res) + try { + const body = await request.json() + // @ts-ignore + const res = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ + data: body, + }) + return NextResponse.json(res) + } catch (error) { + console.error('Error creating survey:', error) + return NextResponse.json({ error: 'Failed to create survey' }, { status: 500 }) + } } export async function GET(request: Request) { - const { searchParams } = new URL(request.url) - const keyword = searchParams.get('keyword') - const searchOption = searchParams.get('searchOption') - const isMySurvey = searchParams.get('isMySurvey') - const sort = searchParams.get('sort') - const offset = searchParams.get('offset') - const store = searchParams.get('store') - const construction_point = searchParams.get('construction_point') - - const searchOptions = ['BUILDING_NAME', 'REPRESENTATIVE', 'STORE', 'CONSTRUCTION_POINT', 'CUSTOMER_NAME', 'POST_CODE', 'ADDRESS', 'ADDRESS_DETAIL'] try { - const where: any = {} - - if (isMySurvey !== null && isMySurvey !== undefined) { - where.REPRESENTATIVE = isMySurvey + const { searchParams } = new URL(request.url) + const params: SearchParams = { + keyword: searchParams.get('keyword'), + searchOption: searchParams.get('searchOption'), + isMySurvey: searchParams.get('isMySurvey'), + sort: searchParams.get('sort'), + offset: searchParams.get('offset'), + memberRole: searchParams.get('memberRole'), + store: searchParams.get('store'), + builderNo: searchParams.get('builderNo'), } - if (keyword && keyword.trim() !== '' && searchOption) { - if (searchOption === 'all') { - where.OR = [] - if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) { - where.OR.push({ - ID: { - equals: Number(keyword), - }, - }) - } - where.OR.push( - ...searchOptions.map((field) => ({ - [field]: { - contains: keyword, - }, - })), - ) - } else if (searchOptions.includes(searchOption)) { - where[searchOption] = { - contains: keyword, - } - } else if (searchOption === 'id') { - where[searchOption] = { - equals: Number(keyword), - } - } + const where: WhereCondition = {} + + // 내가 작성한 매물 조건 + if (params.isMySurvey) { + where.REPRESENTATIVE = params.isMySurvey } - where.AND = [] - if (store) { - where.AND.push({ - STORE: { - equals: store, - }, - }) - } - if (construction_point) { - where.AND.push({ - CONSTRUCTION_POINT: { - equals: construction_point, - }, - }) + // 키워드 검색 조건 + if (params.keyword && params.searchOption) { + Object.assign(where, createKeywordSearchCondition(params.keyword, params.searchOption)) } - if (offset) { + // 회원 유형 조건 + Object.assign(where, createMemberRoleCondition(params)) + + // 데이터 조회 + if (params.offset) { // @ts-ignore const res = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ where, - orderBy: sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' }, - skip: Number(offset), - take: 10, + orderBy: params.sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' }, + skip: Number(params.offset), + take: ITEMS_PER_PAGE, }) return NextResponse.json(res) - } else { - // @ts-ignore - const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ - where, - }) - return NextResponse.json(count) } + + // 전체 개수 조회 + // @ts-ignore + const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where }) + return NextResponse.json(count) } catch (error) { - console.error(error) - throw error + console.error('Error fetching surveys:', error) + return NextResponse.json({ error: 'Failed to fetch surveys' }, { status: 500 }) } } export async function PUT(request: Request) { - const body = await request.json() - const detailInfo = { ...body.detail_info, BASIC_INFO_ID: body.id } - // @ts-ignore - const res = await prisma.SD_SURVEY_SALES_DETAIL_INFO.create({ - data: detailInfo, - }) - return NextResponse.json({ message: 'Survey sales updated successfully' }) + try { + const body = await request.json() + const detailInfo = { ...body.detail_info, BASIC_INFO_ID: body.id } + // @ts-ignore + const res = await prisma.SD_SURVEY_SALES_DETAIL_INFO.create({ + data: detailInfo, + }) + return NextResponse.json({ message: 'Survey sales updated successfully' }) + } catch (error) { + console.error('Error updating survey:', error) + return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 }) + } } diff --git a/src/components/survey-sale/detail/DetailButton.tsx b/src/components/survey-sale/detail/DetailButton.tsx index a2fc44f..d8471c9 100644 --- a/src/components/survey-sale/detail/DetailButton.tsx +++ b/src/components/survey-sale/detail/DetailButton.tsx @@ -8,20 +8,20 @@ export default function DetailButton({ isTemporary, surveyId, representative }: const router = useRouter() const { session } = useSessionStore() const { submitSurvey, deleteSurvey } = useServey(surveyId) - const [userNm, setUserNm] = useState('') + const [userId, setUserId] = useState('') useEffect(() => { if (session?.isLoggedIn) { - setUserNm(session?.userNm ?? '') + setUserId(session?.userId ?? '') } - }, [session, setUserNm]) + }, [session, setUserId]) const handleSubmit = async () => { if (isTemporary) { alert('一時保存されたデータは提出できません。') return } - if (userNm === representative) { + if (userId === representative) { if (confirm('提出しますか??')) { if (surveyId) { // TODO: 제출 페이지 추가 @@ -34,7 +34,7 @@ export default function DetailButton({ isTemporary, surveyId, representative }: } } const handleUpdate = () => { - if (userNm === representative) { + if (userId === representative) { router.push(`/survey-sale/basic-info?id=${surveyId}&isTemp=${isTemporary}`) } else { alert('担当者のみ修正可能です。') @@ -43,7 +43,7 @@ export default function DetailButton({ isTemporary, surveyId, representative }: const handleDelete = async () => { if (confirm('削除しますか?')) { if (surveyId) { - if (userNm === representative) { + if (userId === representative) { await deleteSurvey() router.push('/survey-sale') } else { diff --git a/src/components/survey-sale/detail/form/BasicForm.tsx b/src/components/survey-sale/detail/form/BasicForm.tsx index 6ed3d28..a8dd228 100644 --- a/src/components/survey-sale/detail/form/BasicForm.tsx +++ b/src/components/survey-sale/detail/form/BasicForm.tsx @@ -37,7 +37,7 @@ export default function BasicForm() { const [basicInfoData, setBasicInfoData] = useState(defaultBasicInfoForm) const { addressData } = useAddressStore() - const { userType, store, construction_point } = useUserType() + const { memberRole, store, builderNo } = useUserType() const { session } = useSessionStore() const popupController = usePopupController() @@ -58,11 +58,13 @@ export default function BasicForm() { if (session?.isLoggedIn) { setBasicInfoData((prev) => ({ ...prev, - REPRESENTATIVE: session?.userNm ?? '', + REPRESENTATIVE: session?.userId ?? '', + STORE: store ?? '', + CONSTRUCTION_POINT: builderNo ?? '', })) } setBasicInfoSelected() - }, [surveyDetail, addressData, session?.isLoggedIn, session?.userNm]) + }, [surveyDetail, addressData, session?.isLoggedIn, session?.userNm, store, builderNo]) const focusInput = (input: keyof SurveyBasicRequest) => { const inputElement = document.getElementById(input) @@ -117,11 +119,11 @@ export default function BasicForm() { type="text" className="input-frame" id="representative" - value={session?.userNm ? session?.userNm : basicInfoData.REPRESENTATIVE} + value={session?.userId ? session?.userId : basicInfoData.REPRESENTATIVE} onChange={(e) => handleChange('REPRESENTATIVE', e.target.value)} />
    - {(userType === 'order' || userType?.includes('musubi')) && ( + {(memberRole === 'Builder' || memberRole?.includes('Admin')) && ( <>
    販売店
    @@ -135,14 +137,14 @@ export default function BasicForm() {
    )} - {(userType === 'partner' || userType === 'musubi_con') && ( + {(memberRole === 'Partner' || memberRole === 'Builder') && (
    施工店
    handleChange('CONSTRUCTION_POINT', e.target.value)} />
    diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index 63eb41b..2523aaa 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -6,7 +6,8 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import SearchForm from './SearchForm' import { useSurveyFilterStore } from '@/store/surveyFilterStore' -import { UserType } from '@/hooks/useUserType' +import { MemberRole } from '@/hooks/useUserType' +import { useSessionStore } from '@/store/session' export default function ListTable() { const router = useRouter() @@ -15,7 +16,9 @@ export default function ListTable() { const [heldSurveyList, setHeldSurveyList] = useState([]) const [hasMore, setHasMore] = useState(false) - const [memberType, setMemberType] = useState('hwj') + const [memberRole, setMemberRole] = useState() + + const { session } = useSessionStore() useEffect(() => { if (surveyList && surveyList.length > 0) { @@ -29,9 +32,8 @@ export default function ListTable() { } setHasMore(surveyListCount > offset + 10) } - // 회원 유형 설정 이후 변경 - setMemberType('hwj') - }, [surveyList, surveyListCount, offset]) + setMemberRole(session.role as MemberRole) + }, [surveyList, surveyListCount, offset, session]) const handleDetailClick = (id: number) => { router.push(`/survey-sale/${id}`) @@ -45,7 +47,7 @@ export default function ListTable() { return ( <> - + {heldSurveyList.length > 0 ? (
      diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 6cd8b57..1b46349 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -1,11 +1,11 @@ 'use client' import { SEARCH_OPTIONS, SEARCH_OPTIONS_ENUM, SEARCH_OPTIONS_PARTNERS, useSurveyFilterStore } from '@/store/surveyFilterStore' -import { UserType } from '@/hooks/useUserType' +import { MemberRole } from '@/hooks/useUserType' import { useRouter } from 'next/navigation' import { useState } from 'react' -export default function SearchForm({ onItemsInit, memberType }: { onItemsInit: () => void; memberType: UserType }) { +export default function SearchForm({ onItemsInit, memberRole }: { onItemsInit: () => void; memberRole: MemberRole }) { const router = useRouter() const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore() const [searchKeyword, setSearchKeyword] = useState(keyword) @@ -20,7 +20,7 @@ export default function SearchForm({ onItemsInit, memberType }: { onItemsInit: ( setKeyword(searchKeyword) onItemsInit() } - const searchOptions = memberType === 'partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS + const searchOptions = memberRole === 'Partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS return (
      diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index ea04db4..909d220 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -41,13 +41,13 @@ export function useServey(id?: number): { } { const queryClient = useQueryClient() const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore() - const { store, construction_point } = useUserType() + const { store, builderNo, memberRole } = useUserType() const { data: surveyList, isLoading: isLoadingSurveyList } = useQuery({ - queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, store, construction_point], + queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, store, builderNo, memberRole], queryFn: async () => { const resp = await axiosInstance(null).get('/api/survey-sales', { - params: { keyword, searchOption, isMySurvey, sort, offset, store, construction_point }, + params: { keyword, searchOption, isMySurvey, sort, offset, store, builderNo, memberRole }, }) return resp.data }, diff --git a/src/hooks/useUserType.ts b/src/hooks/useUserType.ts index e8a9dcd..5f38e58 100644 --- a/src/hooks/useUserType.ts +++ b/src/hooks/useUserType.ts @@ -1,24 +1,31 @@ import { useSessionStore } from '@/store/session' import { useEffect, useState } from 'react' -export type UserType = 'hwj' | 'order' | 'musubi' | 'musubi_con' | 'partner' +export type MemberRole = 'T01' | 'Admin' | 'Admin_Sub' | 'Builder' | 'Partner' | 'User' +// TO1 - else +// Admin - 1차점, Admin_Sub - 2차점, Builder - 2차점 시공사, Partner - 파트너사, User - else // 로그인 된 회원 유형에 따라 조사 매물 목록 변경됨 export function useUserType() { const { session } = useSessionStore() - const [userType, setUserType] = useState(null) + const [memberRole, setMemberRole] = useState(null) const [store, setStore] = useState(null) - const [construction_point, setConstructionPoint] = useState(null) + const [builderNo, setBuilderNo] = useState(null) - // TODO: 회원 유형 설정 useEffect(() => { if (!session?.isLoggedIn) return - setUserType('musubi_con') + setMemberRole(session.role as MemberRole) + if (session.role === 'T01') { + setStore(null) + } else { + setStore(session.storeId) + } + setBuilderNo(session.builderNo) }, [session]) return { - userType, + memberRole, store, - construction_point, + builderNo, } } From d7aca7e2146da83239f158928b7d1bf226053042 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Thu, 15 May 2025 16:55:36 +0900 Subject: [PATCH 27/33] feat: enhance survey sales API with detailed error handling, improved search parameters, and new registration form component --- src/app/api/survey-sales/[id]/route.ts | 152 ++++++---- src/app/api/survey-sales/route.ts | 210 +++++++------ src/app/survey-sale/regist/page.tsx | 9 + src/components/popup/ZipCodePopup.tsx | 5 +- src/components/survey-sale/common/NavTab.tsx | 40 ++- .../survey-sale/detail/DataTable.tsx | 26 +- .../survey-sale/detail/DetailButton.tsx | 129 +++++--- .../survey-sale/detail/DetailForm.tsx | 11 +- .../survey-sale/detail/RoofDetailForm.tsx | 11 +- .../survey-sale/detail/form/BasicForm.tsx | 47 ++- .../survey-sale/detail/form/RoofInfoForm.tsx | 6 +- .../form/{ => etcProcess}/MultiCheckEtc.tsx | 0 .../detail/form/{ => etcProcess}/RadioEtc.tsx | 0 .../form/{ => etcProcess}/SelectBoxEtc.tsx | 0 src/components/survey-sale/list/ListTable.tsx | 7 +- .../survey-sale/list/SearchForm.tsx | 33 +- .../survey-sale/temp/basicRegist.tsx | 156 ++++++++++ .../survey-sale/temp/formButton.tsx | 92 ++++++ .../survey-sale/temp/registForm.tsx | 105 +++++++ .../survey-sale/temp/roofRegist.tsx | 284 ++++++++++++++++++ src/hooks/useSurvey.ts | 101 +++++-- src/hooks/useUserType.ts | 31 -- src/store/surveyFilterStore.ts | 6 - src/types/Survey.ts | 15 + 24 files changed, 1126 insertions(+), 350 deletions(-) create mode 100644 src/app/survey-sale/regist/page.tsx rename src/components/survey-sale/detail/form/{ => etcProcess}/MultiCheckEtc.tsx (100%) rename src/components/survey-sale/detail/form/{ => etcProcess}/RadioEtc.tsx (100%) rename src/components/survey-sale/detail/form/{ => etcProcess}/SelectBoxEtc.tsx (100%) create mode 100644 src/components/survey-sale/temp/basicRegist.tsx create mode 100644 src/components/survey-sale/temp/formButton.tsx create mode 100644 src/components/survey-sale/temp/registForm.tsx create mode 100644 src/components/survey-sale/temp/roofRegist.tsx delete mode 100644 src/hooks/useUserType.ts diff --git a/src/app/api/survey-sales/[id]/route.ts b/src/app/api/survey-sales/[id]/route.ts index 4794a5f..3da5981 100644 --- a/src/app/api/survey-sales/[id]/route.ts +++ b/src/app/api/survey-sales/[id]/route.ts @@ -1,40 +1,62 @@ import { NextResponse } from 'next/server' +import { prisma } from '@/libs/prisma' export async function GET(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - // @ts-ignore - const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({ - where: { ID: Number(id) }, - include: { - DETAIL_INFO: true, - }, - }) - return NextResponse.json(survey) -} - -export async function PUT(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - const body = await request.json() try { + const { id } = await context.params // @ts-ignore - const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ + const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({ where: { ID: Number(id) }, - data: { - ...body, + include: { + DETAIL_INFO: true, }, }) return NextResponse.json(survey) } catch (error) { - console.error(error) - throw error + console.error('Error fetching survey:', error) + return NextResponse.json({ error: 'Failed to fetch survey' }, { status: 500 }) + } +} + +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const { id } = await context.params + const body = await request.json() + console.log('body:: ', body) + + // DETAIL_INFO를 분리 + const { DETAIL_INFO, ...basicInfo } = body + + // @ts-ignore + const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ + where: { ID: Number(id) }, + data: { + ...basicInfo, + UPT_DT: new Date(), + DETAIL_INFO: DETAIL_INFO + ? { + upsert: { + create: DETAIL_INFO, + update: DETAIL_INFO, + }, + } + : undefined, + }, + include: { + DETAIL_INFO: true, + }, + }) + return NextResponse.json(survey) + } catch (error) { + console.error('Error updating survey:', error) + return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 }) } } export async function DELETE(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - try { - //@ts-ignore + const { id } = await context.params + await prisma.$transaction(async (tx) => { // @ts-ignore const detailData = await tx.SD_SURVEY_SALES_BASIC_INFO.findUnique({ @@ -43,69 +65,75 @@ export async function DELETE(request: Request, context: { params: { id: string } DETAIL_INFO: true, }, }) - console.log('detailData:: ', detailData) + if (detailData?.DETAIL_INFO?.ID) { // @ts-ignore await tx.SD_SURVEY_SALES_DETAIL_INFO.delete({ - where: { ID: Number(detailData?.DETAIL_INFO?.ID) }, + where: { ID: Number(detailData.DETAIL_INFO.ID) }, }) } + // @ts-ignore await tx.SD_SURVEY_SALES_BASIC_INFO.delete({ where: { ID: Number(id) }, }) }) + return NextResponse.json({ message: 'Survey deleted successfully' }) } catch (error) { - console.error(error) - throw error + console.error('Error deleting survey:', error) + return NextResponse.json({ error: 'Failed to delete survey' }, { status: 500 }) } } export async function PATCH(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - const body = await request.json() + try { + const { id } = await context.params + const body = await request.json() - if (body.submit) { - // @ts-ignore - const survey = await prisma.SD_SURVEY_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_SURVEY_SALES_DETAIL_INFO.findUnique({ - where: { BASIC_INFO_ID: Number(id) }, - }) - console.log('hasDetails:: ', hasDetails) - 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 { + if (body.submit) { // @ts-ignore const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ where: { ID: Number(id) }, data: { - DETAIL_INFO: { - create: body.DETAIL_INFO, - }, + SUBMISSION_STATUS: true, + SUBMISSION_DATE: new Date(), }, }) - console.log(survey) - return NextResponse.json({ message: 'Survey detail created successfully' }) + 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' }) + } } + } 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 1343880..d4b676a 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -1,16 +1,18 @@ import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' -// Types +/** + * 검색 파라미터 + */ type SearchParams = { - keyword?: string | null - searchOption?: string | null - isMySurvey?: string | null - sort?: string | null + keyword?: string | null // 검색어 + searchOption?: string | null // 검색 옵션 (select 옵션) + isMySurvey?: string | null // 내가 작성한 매물 + sort?: string | null // 정렬 방식 offset?: string | null - memberRole?: string | null - store?: string | null - builderNo?: string | null + role?: string | null // 회원권한한 + store?: string | null // 판매점ID + builderNo?: string | null // 시공ID } type WhereCondition = { @@ -19,133 +21,131 @@ type WhereCondition = { [key: string]: any } -// Constants +// 검색 가능한 필드 옵션 const SEARCH_OPTIONS = [ - 'BUILDING_NAME', - 'REPRESENTATIVE', - 'STORE', - 'CONSTRUCTION_POINT', - 'CUSTOMER_NAME', - 'POST_CODE', - 'ADDRESS', - 'ADDRESS_DETAIL', + 'BUILDING_NAME', // 건물명 + 'REPRESENTATIVE', // 담당자 + 'STORE', // 판매점 + 'CONSTRUCTION_POINT', // 시공점 + 'CUSTOMER_NAME', // 고객명 + 'POST_CODE', // 우편번호 + 'ADDRESS', // 주소 + 'ADDRESS_DETAIL', // 상세주소 ] as const +// 페이지당 항목 수 const ITEMS_PER_PAGE = 10 -// Helper functions +/** + * 키워드 검색 조건 생성 함수 + * @param keyword 검색 키워드 + * @param searchOption 검색 옵션 + * @returns 검색 조건 객체 + */ const createKeywordSearchCondition = (keyword: string, searchOption: string): WhereCondition => { const where: WhereCondition = {} if (searchOption === 'all') { + // 모든 필드 검색 시 OR 조건 사용 where.OR = [] - // ID 검색 조건 추가 + // ID가 숫자인 경우 ID 검색 조건 추가 if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) { where.OR.push({ ID: { equals: Number(keyword) }, }) } - // 다른 필드 검색 조건 추가 where.OR.push( ...SEARCH_OPTIONS.map((field) => ({ [field]: { contains: keyword }, })), ) - } else if (SEARCH_OPTIONS.includes(searchOption as any)) { - where[searchOption] = { contains: keyword } + } else if (SEARCH_OPTIONS.includes(searchOption.toUpperCase() as any)) { + // 특정 필드 검색 + where[searchOption.toUpperCase()] = { contains: keyword } } else if (searchOption === 'id') { - where.ID = { equals: Number(keyword) } + // ID 검색 (숫자 변환 필요) + const number = Number(keyword) + if (!isNaN(number)) { + where.ID = { equals: number } + } else { + // 유효하지 않은 ID 검색 시 빈 결과 반환 + where.ID = { equals: null } + } } return where } +/** + * 회원 역할별 검색 조건 생성 함수 + * @param params 검색 파라미터 + * @returns 검색 조건 객체 + */ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { const where: WhereCondition = { AND: [] } - switch (params.memberRole) { - // 1차점: 같은 판매점에서 작성된 매물 + 2차점에서 제출받은 매물 - case 'Admin': + switch (params.role) { + case 'Admin': // 1차점 + // 같은 판매점에서 작성된 매물 + 2차점에서 제출받은 매물 where.OR = [ { - AND: [ - { STORE: { equals: params.store } }, - { SUBMISSION_STATUS: { equals: false } } - ] + AND: [{ STORE: { equals: params.store } }], }, { - AND: [ - { STORE: { endsWith: params.store } }, - { SUBMISSION_STATUS: { equals: true } } - ] - } + AND: [{ STORE: { startsWith: params.store } }, { SUBMISSION_STATUS: { equals: true } }], + }, ] break - // 2차점: 같은 판매점에서 작성된 매물 + Builder에게 제출받은 매물 - case 'Admin_Sub': + case 'Admin_Sub': // 2차점 where.OR = [ { AND: [ { STORE: { equals: params.store } }, - { CONSTRUCTION_POINT: { equals: null } }, - { SUBMISSION_STATUS: { equals: false } } + { + OR: [ + { CONSTRUCTION_POINT: { equals: null } }, + { CONSTRUCTION_POINT: { equals: '' } } + ] + } ] }, { AND: [ { STORE: { equals: params.store } }, { CONSTRUCTION_POINT: { not: null } }, + { CONSTRUCTION_POINT: { not: '' } }, { SUBMISSION_STATUS: { equals: true } } ] } ] break - // 2차점 시공권한: 같은 시공ID에서 작성된 매물 - case 'Builder': + case 'Builder': // 2차점 시공권한 + case 'Partner': // Partner + // 같은 시공ID에서 작성된 매물 where.AND?.push({ CONSTRUCTION_POINT: { equals: params.builderNo }, - SUBMISSION_STATUS: { equals: false }, }) break - // 시공점: 같은 시공ID에서 작성된 매물 - case 'Partner': - where.AND?.push({ - CONSTRUCTION_POINT: { equals: params.builderNo }, - SUBMISSION_STATUS: { equals: false }, - }) - break - - // 모든 매물 조회 가능 case 'T01': case 'User': + // 모든 매물 조회 가능 (추가 조건 없음) break } return where } -// API Routes -export async function POST(request: Request) { - try { - const body = await request.json() - // @ts-ignore - const res = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ - data: body, - }) - return NextResponse.json(res) - } catch (error) { - console.error('Error creating survey:', error) - return NextResponse.json({ error: 'Failed to create survey' }, { status: 500 }) - } -} - +/** + * GET 핸들러 - 설문 목록 조회 + */ export async function GET(request: Request) { try { + // URL 파라미터 파싱 const { searchParams } = new URL(request.url) const params: SearchParams = { keyword: searchParams.get('keyword'), @@ -153,59 +153,95 @@ export async function GET(request: Request) { isMySurvey: searchParams.get('isMySurvey'), sort: searchParams.get('sort'), offset: searchParams.get('offset'), - memberRole: searchParams.get('memberRole'), + role: searchParams.get('role'), store: searchParams.get('store'), builderNo: searchParams.get('builderNo'), } + // 검색 조건 구성 const where: WhereCondition = {} - // 내가 작성한 매물 조건 + // 내가 작성한 매물 조건 적용 if (params.isMySurvey) { where.REPRESENTATIVE = params.isMySurvey } - // 키워드 검색 조건 + // 키워드 검색 조건 적용 if (params.keyword && params.searchOption) { Object.assign(where, createKeywordSearchCondition(params.keyword, params.searchOption)) } - // 회원 유형 조건 + // 회원 유형 조건 적용 Object.assign(where, createMemberRoleCondition(params)) - // 데이터 조회 + // 데이터 조회 또는 카운트 if (params.offset) { - // @ts-ignore - const res = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ + // 페이지네이션 데이터 조회 + //@ts-ignore + const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ where, orderBy: params.sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' }, skip: Number(params.offset), take: ITEMS_PER_PAGE, }) - return NextResponse.json(res) + return NextResponse.json(surveys) + } else { + // 전체 개수만 조회 + //@ts-ignore + const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where }) + return NextResponse.json(count) } - - // 전체 개수 조회 - // @ts-ignore - const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where }) - return NextResponse.json(count) } catch (error) { - console.error('Error fetching surveys:', error) - return NextResponse.json({ error: 'Failed to fetch surveys' }, { status: 500 }) + console.error(error) + return NextResponse.json({ error: 'Fail Read Survey' }, { status: 500 }) } } +/** + * PUT 핸들러 - 상세 정보 추가 + */ export async function PUT(request: Request) { try { const body = await request.json() - const detailInfo = { ...body.detail_info, BASIC_INFO_ID: body.id } - // @ts-ignore - const res = await prisma.SD_SURVEY_SALES_DETAIL_INFO.create({ + + // 상세 정보 생성을 위한 데이터 구성 + const detailInfo = { + ...body.detail_info, + BASIC_INFO_ID: body.id, + } + + // 상세 정보 생성 + //@ts-ignore + await prisma.SD_SURVEY_SALES_DETAIL_INFO.create({ data: detailInfo, }) - return NextResponse.json({ message: 'Survey sales updated successfully' }) + + return NextResponse.json({ + message: 'Success Update Survey', + }) } catch (error) { - console.error('Error updating survey:', error) - return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 }) + console.error(error) + return NextResponse.json({ error: 'Fail Update Survey' }, { status: 500 }) + } +} + +export async function POST(request: Request) { + try { + const body = await request.json() + const { DETAIL_INFO, ...basicInfo } = body + // 기본 정보 생성 + //@ts-ignore + const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ + data: { + ...basicInfo, + DETAIL_INFO: { + create: DETAIL_INFO, + }, + }, + }) + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Fail Create Survey' }, { status: 500 }) } } diff --git a/src/app/survey-sale/regist/page.tsx b/src/app/survey-sale/regist/page.tsx new file mode 100644 index 0000000..5090aaa --- /dev/null +++ b/src/app/survey-sale/regist/page.tsx @@ -0,0 +1,9 @@ +import RegistForm from '@/components/survey-sale/temp/registForm' + +export default function RegistPage() { + return ( + <> + + + ) +} diff --git a/src/components/popup/ZipCodePopup.tsx b/src/components/popup/ZipCodePopup.tsx index 2fec15c..4923833 100644 --- a/src/components/popup/ZipCodePopup.tsx +++ b/src/components/popup/ZipCodePopup.tsx @@ -28,10 +28,11 @@ export default function ZipCodePopup() { const popupController = usePopupController() const handleApply = () => { + console.log(addressInfo?.[0]) setAddressData({ post_code: addressInfo?.[0]?.zipcode || '', - address: addressInfo?.[0]?.prefcode || '', - address_detail: addressInfo?.[0]?.address1 + ' ' + addressInfo?.[0]?.address2 + ' ' + addressInfo?.[0]?.address3 || '', + address: addressInfo?.[0]?.address1 || '', + address_detail: addressInfo?.[0]?.address2 + ' ' + addressInfo?.[0]?.address3 || '', }) popupController.setZipCodePopup(false) } diff --git a/src/components/survey-sale/common/NavTab.tsx b/src/components/survey-sale/common/NavTab.tsx index 4131804..ffa6ea7 100644 --- a/src/components/survey-sale/common/NavTab.tsx +++ b/src/components/survey-sale/common/NavTab.tsx @@ -15,7 +15,7 @@ export default function NavTab() { const params = useParams() const detailId = params.id - const { basicInfoSelected, roofInfoSelected, reset } = useSurveySaleTabState() + const { basicInfoSelected, roofInfoSelected, reset, setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState() useEffect(() => { return () => { @@ -27,26 +27,36 @@ export default function NavTab() { return null } - const handleBasicInfoClick = () => { - if (id) { - router.push(`/survey-sale/basic-info?id=${id}`) - return + const scrollSection = (section: string) => { + const sectionElement = document.getElementById(section) + if (sectionElement) { + sectionElement.scrollIntoView({ behavior: 'smooth' }) } + } + + const handleBasicInfoClick = () => { + // if (id) { + // router.push(`/survey-sale/basic-info?id=${id}`) + // return + // } if (detailId) { router.push(`/survey-sale/${detailId}`) return } + scrollSection('basic-form-section') + + setBasicInfoSelected() } const handleRoofInfoClick = () => { - if (id) { - if (isTemp === 'true') { - alert('基本情報が一時保存された状態です。') - return - } - router.push(`/survey-sale/roof-info?id=${id}`) - return - } + // if (id) { + // if (isTemp === 'true') { + // alert('基本情報が一時保存された状態です。') + // return + // } + // router.push(`/survey-sale/roof-info?id=${id}`) + // return + // } if (detailId) { router.push(`/survey-sale/${detailId}?tab=roof-info`) return @@ -55,6 +65,10 @@ export default function NavTab() { alert('基本情報を先に保存してください。') return null } + // if (pathname === '/survey-sale/regist') { + scrollSection('roof-form-section') + // } + setRoofInfoSelected() } return ( diff --git a/src/components/survey-sale/detail/DataTable.tsx b/src/components/survey-sale/detail/DataTable.tsx index 2ec528e..e7ce554 100644 --- a/src/components/survey-sale/detail/DataTable.tsx +++ b/src/components/survey-sale/detail/DataTable.tsx @@ -2,10 +2,8 @@ import { useServey } from '@/hooks/useSurvey' import { useParams, useSearchParams } from 'next/navigation' -import { useEffect } from 'react' -import { useState } from 'react' +import { useEffect, useState } from 'react' import DetailForm from './DetailForm' -import { useSurveySaleTabState } from '@/store/surveySaleTabState' import RoofDetailForm from './RoofDetailForm' export default function DataTable() { @@ -14,21 +12,21 @@ export default function DataTable() { const searchParams = useSearchParams() const tab = searchParams.get('tab') + const isTemp = searchParams.get('isTemporary') const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id)) - const [isTemporary, setIsTemporary] = useState(true) - const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState() + const [isTemporary, setIsTemporary] = useState(isTemp === 'true') + + const { validateSurveyDetail } = useServey(Number(id)) useEffect(() => { - if (surveyDetail?.REPRESENTATIVE && surveyDetail?.STORE && surveyDetail?.CONSTRUCTION_POINT) { - setIsTemporary(false) + if (surveyDetail?.DETAIL_INFO) { + const validate = validateSurveyDetail(surveyDetail.DETAIL_INFO) + if (validate.trim() !== '') { + setIsTemporary(false) + } } - if (tab === 'roof-info') { - setRoofInfoSelected() - } else { - setBasicInfoSelected() - } - }, [surveyDetail, tab, setBasicInfoSelected, setRoofInfoSelected]) + }, [surveyDetail]) if (isLoadingSurveyDetail) { return
      Loading...
      @@ -68,7 +66,7 @@ export default function DataTable() { <> {/* TODO: 제출한 판매점 ID 추가 필요 */}
      {new Date(surveyDetail.SUBMISSION_DATE).toLocaleString()}
      -
      販売店 ID...
      +
      {surveyDetail.STORE}
      ) : ( '-' diff --git a/src/components/survey-sale/detail/DetailButton.tsx b/src/components/survey-sale/detail/DetailButton.tsx index d8471c9..3a871bf 100644 --- a/src/components/survey-sale/detail/DetailButton.tsx +++ b/src/components/survey-sale/detail/DetailButton.tsx @@ -1,86 +1,119 @@ 'use client' -import { useRouter } from 'next/navigation' +import { useRouter, useSearchParams } from 'next/navigation' import { useServey } from '@/hooks/useSurvey' import { useSessionStore } from '@/store/session' -import { useEffect, useState } from 'react' +import { SurveyBasicInfo } from '@/types/Survey' +import { useState } from 'react' -export default function DetailButton({ isTemporary, surveyId, representative }: { isTemporary: boolean; surveyId: number; representative: string }) { +export default function DetailButton({ surveyDetail }: { surveyDetail: SurveyBasicInfo | null }) { const router = useRouter() const { session } = useSessionStore() - const { submitSurvey, deleteSurvey } = useServey(surveyId) - const [userId, setUserId] = useState('') + const { submitSurvey, deleteSurvey } = useServey(surveyDetail?.ID ?? 0) - useEffect(() => { - if (session?.isLoggedIn) { - setUserId(session?.userId ?? '') + const searchParams = useSearchParams() + const isTemp = searchParams.get('isTemporary') + const [isTemporary, setIsTemporary] = useState(isTemp === 'true') + + const checkRole = () => { + switch (session?.role) { + case 'T01': + return session?.userNm === surveyDetail?.REPRESENTATIVE ? true : false + case 'Admin': + return session?.storeNm === surveyDetail?.STORE ? true : false + case 'Admin_Sub': + return session?.storeNm === surveyDetail?.STORE ? true : false + case 'Builder': + return session?.builderNo === surveyDetail?.CONSTRUCTION_POINT ? true : false + case 'Partner': + return session?.builderNo === surveyDetail?.CONSTRUCTION_POINT ? true : false + default: + return '' } - }, [session, setUserId]) + } const handleSubmit = async () => { - if (isTemporary) { - alert('一時保存されたデータは提出できません。') - return - } - if (userId === representative) { - if (confirm('提出しますか??')) { - if (surveyId) { - // TODO: 제출 페이지 추가 - alert('SUBMIT POPUP!!!!!!!!!!!') - await submitSurvey() - } + const result = checkRole() + if (result) { + if (isTemporary) { + alert('一時保存されたデータは提出できません。') + return } - } else { - alert('担当者のみ提出可能です。') + window.neoConfirm( + '提出しますか??', + async () => { + if (surveyDetail?.ID) { + // TODO: 제출 페이지 추가 + alert('SUBMIT POPUP!!!!!!!!!!!') + await submitSurvey() + } + }, + () => null, + ) } } const handleUpdate = () => { - if (userId === representative) { - router.push(`/survey-sale/basic-info?id=${surveyId}&isTemp=${isTemporary}`) + const result = checkRole() + if (result) { + // router.push(`/survey-sale/basic-info?id=${surveyDetail?.ID}&isTemp=${isTemporary}`) + router.push(`/survey-sale/regist?id=${surveyDetail?.ID}`) } else { alert('担当者のみ修正可能です。') } } const handleDelete = async () => { - if (confirm('削除しますか?')) { - if (surveyId) { - if (userId === representative) { - await deleteSurvey() - router.push('/survey-sale') - } else { - alert('担当者のみ削除可能です。') + window.neoConfirm( + '削除しますか?', + async () => { + if (surveyDetail?.ID) { + if (session.userNm === surveyDetail?.REPRESENTATIVE) { + await deleteSurvey() + alert('削除されました。') + router.push('/survey-sale') + } else { + alert('担当者のみ削除可能です。') + } } - } - } + }, + () => null, + ) } + const isSubmitter = session?.storeNm === surveyDetail?.STORE && session?.builderNo === surveyDetail?.CONSTRUCTION_POINT + return (
      - {isTemporary ? ( +
      + +
      + {isSubmitter && surveyDetail?.SUBMISSION_STATUS ? ( <> ) : ( <> + {isTemporary || surveyDetail?.SUBMISSION_STATUS ? ( + <> + ) : ( + <> +
      + +
      + + )}
      -
      -
      )} -
      - -
      -
      - -
      ) } diff --git a/src/components/survey-sale/detail/DetailForm.tsx b/src/components/survey-sale/detail/DetailForm.tsx index 469c465..73ce5c7 100644 --- a/src/components/survey-sale/detail/DetailForm.tsx +++ b/src/components/survey-sale/detail/DetailForm.tsx @@ -1,6 +1,5 @@ 'use client' -import { useEffect, useState } from 'react' import DetailButton from './DetailButton' import { SurveyBasicInfo } from '@/types/Survey' @@ -11,14 +10,6 @@ export default function DetailForm({ surveyDetail: SurveyBasicInfo | null isLoadingSurveyDetail: boolean }) { - const [isTemporary, setIsTemporary] = useState(true) - - useEffect(() => { - if (surveyDetail?.REPRESENTATIVE && surveyDetail?.STORE && surveyDetail?.CONSTRUCTION_POINT) { - setIsTemporary(false) - } - }, [surveyDetail]) - if (isLoadingSurveyDetail) { return
      Loading...
      } @@ -56,7 +47,7 @@ export default function DetailForm({
    - +
    ) diff --git a/src/components/survey-sale/detail/RoofDetailForm.tsx b/src/components/survey-sale/detail/RoofDetailForm.tsx index a20f48a..a9a4031 100644 --- a/src/components/survey-sale/detail/RoofDetailForm.tsx +++ b/src/components/survey-sale/detail/RoofDetailForm.tsx @@ -1,8 +1,8 @@ import { SurveyBasicInfo, SurveyDetailInfo } from '@/types/Survey' import DetailButton from './DetailButton' -import { roof_material, supplementary_facilities } from './form/MultiCheckEtc' -import { selectBoxOptions } from './form/SelectBoxEtc' -import { radioEtcData } from './form/RadioEtc' +import { roof_material, supplementary_facilities } from './form/etcProcess/MultiCheckEtc' +import { selectBoxOptions } from './form/etcProcess/SelectBoxEtc' +import { radioEtcData } from './form/etcProcess/RadioEtc' export default function RoofDetailForm({ surveyDetail, @@ -196,7 +196,10 @@ export default function RoofDetailForm({
    - +
    ) diff --git a/src/components/survey-sale/detail/form/BasicForm.tsx b/src/components/survey-sale/detail/form/BasicForm.tsx index a8dd228..1f55838 100644 --- a/src/components/survey-sale/detail/form/BasicForm.tsx +++ b/src/components/survey-sale/detail/form/BasicForm.tsx @@ -8,7 +8,7 @@ import { useSurveySaleTabState } from '@/store/surveySaleTabState' import { usePopupController } from '@/store/popupController' import { useAddressStore } from '@/store/addressStore' import { useSessionStore } from '@/store/session' -import { useUserType } from '@/hooks/useUserType' +// import { useUserType } from '@/hooks/useUserType' const defaultBasicInfoForm: SurveyBasicRequest = { REPRESENTATIVE: '', @@ -37,7 +37,6 @@ export default function BasicForm() { const [basicInfoData, setBasicInfoData] = useState(defaultBasicInfoForm) const { addressData } = useAddressStore() - const { memberRole, store, builderNo } = useUserType() const { session } = useSessionStore() const popupController = usePopupController() @@ -59,12 +58,12 @@ export default function BasicForm() { setBasicInfoData((prev) => ({ ...prev, REPRESENTATIVE: session?.userId ?? '', - STORE: store ?? '', - CONSTRUCTION_POINT: builderNo ?? '', + STORE: session?.storeNm ?? '', + CONSTRUCTION_POINT: session?.builderNo ?? '', })) } setBasicInfoSelected() - }, [surveyDetail, addressData, session?.isLoggedIn, session?.userNm, store, builderNo]) + }, [surveyDetail, addressData, session?.isLoggedIn, session?.userId, session?.storeNm, session?.builderNo]) const focusInput = (input: keyof SurveyBasicRequest) => { const inputElement = document.getElementById(input) @@ -88,19 +87,19 @@ export default function BasicForm() { const handleSave = async (isTemporary: boolean) => { if (id) { - updateSurvey(basicInfoData) + // updateSurvey(basicInfoData) alert('保存しました。') - router.push(`/survey-sale/${id}?tab=basic-info`) + // router.push(`/survey-sale/${id}?tab=basic-info`) } if (isTemporary) { - const saveId = await createSurvey(basicInfoData) + // const saveId = await createSurvey(basicInfoData) alert('一時保存されました。') - router.push(`/survey-sale/${saveId}?tab=basic-info`) + // router.push(`/survey-sale/${saveId}?tab=basic-info`) } else { if (validateSurvey(basicInfoData)) { - const saveId = await createSurvey(basicInfoData) - alert('保存しました。 登録番号: ' + saveId) - router.push(`/survey-sale/${saveId}?tab=basic-info`) + // const saveId = await createSurvey(basicInfoData) + alert('保存しました。') + // router.push(`/survey-sale/${saveId}?tab=basic-info`) } } } @@ -123,7 +122,7 @@ export default function BasicForm() { onChange={(e) => handleChange('REPRESENTATIVE', e.target.value)} />
- {(memberRole === 'Builder' || memberRole?.includes('Admin')) && ( + {(session?.role === 'Builder' || session?.role?.includes('Admin')) && ( <>
販売店
@@ -131,20 +130,20 @@ export default function BasicForm() { type="text" className="input-frame" id="store" - value={store ? store : basicInfoData.STORE ?? ''} + value={session?.storeNm ? session?.storeNm : basicInfoData.STORE ?? ''} onChange={(e) => handleChange('STORE', e.target.value)} />
)} - {(memberRole === 'Partner' || memberRole === 'Builder') && ( + {(session?.role === 'Partner' || session?.role === 'Builder') && (
施工店
handleChange('CONSTRUCTION_POINT', e.target.value)} />
@@ -196,15 +195,13 @@ export default function BasicForm() {
- +
diff --git a/src/components/survey-sale/detail/form/RoofInfoForm.tsx b/src/components/survey-sale/detail/form/RoofInfoForm.tsx index 582a423..2a465d4 100644 --- a/src/components/survey-sale/detail/form/RoofInfoForm.tsx +++ b/src/components/survey-sale/detail/form/RoofInfoForm.tsx @@ -6,9 +6,9 @@ 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' +import MultiCheckEtc from './etcProcess/MultiCheckEtc' +import SelectBoxEtc from './etcProcess/SelectBoxEtc' +import RadioEtc from './etcProcess/RadioEtc' const defaultDetailInfoForm: SurveyDetailRequest = { CONTRACT_CAPACITY: null, diff --git a/src/components/survey-sale/detail/form/MultiCheckEtc.tsx b/src/components/survey-sale/detail/form/etcProcess/MultiCheckEtc.tsx similarity index 100% rename from src/components/survey-sale/detail/form/MultiCheckEtc.tsx rename to src/components/survey-sale/detail/form/etcProcess/MultiCheckEtc.tsx diff --git a/src/components/survey-sale/detail/form/RadioEtc.tsx b/src/components/survey-sale/detail/form/etcProcess/RadioEtc.tsx similarity index 100% rename from src/components/survey-sale/detail/form/RadioEtc.tsx rename to src/components/survey-sale/detail/form/etcProcess/RadioEtc.tsx diff --git a/src/components/survey-sale/detail/form/SelectBoxEtc.tsx b/src/components/survey-sale/detail/form/etcProcess/SelectBoxEtc.tsx similarity index 100% rename from src/components/survey-sale/detail/form/SelectBoxEtc.tsx rename to src/components/survey-sale/detail/form/etcProcess/SelectBoxEtc.tsx diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index 2523aaa..16351b7 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -6,7 +6,6 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import SearchForm from './SearchForm' import { useSurveyFilterStore } from '@/store/surveyFilterStore' -import { MemberRole } from '@/hooks/useUserType' import { useSessionStore } from '@/store/session' export default function ListTable() { @@ -16,7 +15,6 @@ export default function ListTable() { const [heldSurveyList, setHeldSurveyList] = useState([]) const [hasMore, setHasMore] = useState(false) - const [memberRole, setMemberRole] = useState() const { session } = useSessionStore() @@ -32,8 +30,7 @@ export default function ListTable() { } setHasMore(surveyListCount > offset + 10) } - setMemberRole(session.role as MemberRole) - }, [surveyList, surveyListCount, offset, session]) + }, [surveyList, surveyListCount, offset, session?.role]) const handleDetailClick = (id: number) => { router.push(`/survey-sale/${id}`) @@ -47,7 +44,7 @@ export default function ListTable() { return ( <> - + {heldSurveyList.length > 0 ? (
    diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 1b46349..3f3d234 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -1,23 +1,22 @@ 'use client' import { SEARCH_OPTIONS, SEARCH_OPTIONS_ENUM, SEARCH_OPTIONS_PARTNERS, useSurveyFilterStore } from '@/store/surveyFilterStore' -import { MemberRole } from '@/hooks/useUserType' import { useRouter } from 'next/navigation' import { useState } from 'react' -export default function SearchForm({ onItemsInit, memberRole }: { onItemsInit: () => void; memberRole: MemberRole }) { +export default function SearchForm({ onItemsInit, memberRole, userId }: { onItemsInit: () => void; memberRole: string; userId: string }) { const router = useRouter() const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore() const [searchKeyword, setSearchKeyword] = useState(keyword) - - const username = 'test' + const [option, setOption] = useState(searchOption) const handleSearch = () => { - if (searchKeyword.trim().length < 2) { + if (option !== 'id' && searchKeyword.trim().length < 2) { alert('2文字以上入力してください') return } setKeyword(searchKeyword) + setSearchOption(option) onItemsInit() } const searchOptions = memberRole === 'Partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS @@ -25,7 +24,7 @@ export default function SearchForm({ onItemsInit, memberRole }: { onItemsInit: ( return (
    -
    @@ -34,8 +33,18 @@ export default function SearchForm({ onItemsInit, memberRole }: { onItemsInit: ( className="select-form" name="search-option" id="search-option" - value={searchOption} - onChange={(e) => setSearchOption(e.target.value as SEARCH_OPTIONS_ENUM)} + value={option} + onChange={(e) => { + if (e.target.value === 'all') { + setKeyword('') + setSearchKeyword('') + onItemsInit() + setSearchOption('all') + setOption('all') + } else { + setOption(e.target.value as SEARCH_OPTIONS_ENUM) + } + }} > {searchOptions.map((option) => (
- + ) diff --git a/src/components/survey-sale/detail/form/RoofInfoForm.tsx b/src/components/survey-sale/detail/my/roofInfoForm.tsx similarity index 98% rename from src/components/survey-sale/detail/form/RoofInfoForm.tsx rename to src/components/survey-sale/detail/my/roofInfoForm.tsx index 2a465d4..bd0a1f7 100644 --- a/src/components/survey-sale/detail/form/RoofInfoForm.tsx +++ b/src/components/survey-sale/detail/my/roofInfoForm.tsx @@ -6,9 +6,9 @@ import { useServey } from '@/hooks/useSurvey' import { SurveyDetailRequest } from '@/types/Survey' import { useRouter, useSearchParams } from 'next/navigation' import { useEffect, useState } from 'react' -import MultiCheckEtc from './etcProcess/MultiCheckEtc' -import SelectBoxEtc from './etcProcess/SelectBoxEtc' -import RadioEtc from './etcProcess/RadioEtc' +import MultiCheckEtc from '../form/etcProcess/MultiCheckEtc' +import SelectBoxEtc from '../form/etcProcess/SelectBoxEtc' +import RadioEtc from '../form/etcProcess/RadioEtc' const defaultDetailInfoForm: SurveyDetailRequest = { CONTRACT_CAPACITY: null, From eb1f6b23e76052728eb1dea11085390257c270a3 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Mon, 19 May 2025 13:47:57 +0900 Subject: [PATCH 33/33] refactor: Update Survey components and API routes for improved data handling and structure --- prisma/schema.prisma | 2 +- src/app/api/survey-sales/route.ts | 29 +- src/app/survey-sale/[id]/page.tsx | 19 - src/app/survey-sale/basic-info/page.tsx | 9 - src/app/survey-sale/regist/page.tsx | 3 +- src/app/survey-sale/roof-info/page.tsx | 9 - .../survey-sale/detail/BasicForm.tsx | 24 +- .../survey-sale/detail/DataTable.tsx | 17 +- .../survey-sale/detail/DetailForm.tsx | 109 +++--- .../survey-sale/{ => detail}/RegistForm.tsx | 2 +- .../survey-sale/detail/RoofForm.tsx | 274 ++++++++++++-- .../detail/form/etcProcess/MultiCheckEtc.tsx | 141 ------- .../detail/form/etcProcess/RadioEtc.tsx | 175 --------- .../detail/form/etcProcess/SelectBoxEtc.tsx | 246 ------------ .../survey-sale/detail/my/basicForm.tsx | 244 ------------ .../survey-sale/detail/my/detailButton.tsx | 119 ------ .../survey-sale/detail/my/roofDetailForm.tsx | 256 ------------- .../survey-sale/detail/my/roofInfoForm.tsx | 353 ------------------ src/components/survey-sale/list/ListTable.tsx | 37 +- .../survey-sale/list/SearchForm.tsx | 2 +- .../survey-sale/temp/basicRegist.tsx | 153 -------- .../survey-sale/temp/formButton.tsx | 92 ----- .../survey-sale/temp/registForm.tsx | 105 ------ .../survey-sale/temp/roofRegist.tsx | 284 -------------- src/hooks/useSurvey.ts | 76 ++-- src/libs/axios.ts | 17 +- src/types/Survey.ts | 226 +++++------ 27 files changed, 497 insertions(+), 2526 deletions(-) delete mode 100644 src/app/survey-sale/basic-info/page.tsx delete mode 100644 src/app/survey-sale/roof-info/page.tsx rename src/components/survey-sale/{ => detail}/RegistForm.tsx (94%) delete mode 100644 src/components/survey-sale/detail/form/etcProcess/MultiCheckEtc.tsx delete mode 100644 src/components/survey-sale/detail/form/etcProcess/RadioEtc.tsx delete mode 100644 src/components/survey-sale/detail/form/etcProcess/SelectBoxEtc.tsx delete mode 100644 src/components/survey-sale/detail/my/basicForm.tsx delete mode 100644 src/components/survey-sale/detail/my/detailButton.tsx delete mode 100644 src/components/survey-sale/detail/my/roofDetailForm.tsx delete mode 100644 src/components/survey-sale/detail/my/roofInfoForm.tsx delete mode 100644 src/components/survey-sale/temp/basicRegist.tsx delete mode 100644 src/components/survey-sale/temp/formButton.tsx delete mode 100644 src/components/survey-sale/temp/registForm.tsx delete mode 100644 src/components/survey-sale/temp/roofRegist.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4993d5b..5c4c1d8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -214,6 +214,6 @@ model MS_USR_TRK { OWNER String @db.VarChar(100) TYPE String @db.VarChar(50) URL String? @db.VarChar(200) - DATA String? @db.VarChar(200) REG_DT DateTime @default(now()) + DATA String? @db.VarChar(200) } diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index 23f3d18..a2df29d 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -173,23 +173,18 @@ export async function GET(request: Request) { where.AND.push(roleCondition) } - // 데이터 조회 또는 카운트 - if (params.offset) { - // 페이지네이션 데이터 조회 - //@ts-ignore - const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ - where, - orderBy: params.sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' }, - skip: Number(params.offset), - take: ITEMS_PER_PAGE, - }) - return NextResponse.json(surveys) - } else { - // 전체 개수만 조회 - //@ts-ignore - const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where }) - return NextResponse.json(count) - } + // 페이지네이션 데이터 조회 + //@ts-ignore + const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ + where, + orderBy: params.sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' }, + skip: Number(params.offset), + take: ITEMS_PER_PAGE, + }) + // 전체 개수만 조회 + //@ts-ignore + const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where }) + return NextResponse.json({ data: { data: surveys, count: count } }) } catch (error) { console.error(error) return NextResponse.json({ error: 'Fail Read Survey' }, { status: 500 }) diff --git a/src/app/survey-sale/[id]/page.tsx b/src/app/survey-sale/[id]/page.tsx index 482dab4..ac22151 100644 --- a/src/app/survey-sale/[id]/page.tsx +++ b/src/app/survey-sale/[id]/page.tsx @@ -1,25 +1,6 @@ import DataTable from '@/components/survey-sale/detail/DataTable' -import DetailForm from '@/components/survey-sale/detail/DetailForm' -import { SurveyBasicInfo } from '@/types/Survey' export default function page() { - const surveyInfo: SurveyBasicInfo = { - ID: 1, - REPRESENTATIVE: 'HG', - STORE: 'HWJ(T01)', - CONSTRUCTION_POINT: '施工点名表示', - INVESTIGATION_DATE: '2021-01-01', - BUILDING_NAME: 'ビル名表示', - CUSTOMER_NAME: '顧客名表示', - POST_CODE: '1234567890', - ADDRESS: '東京都千代田区永田町1-7-1', - ADDRESS_DETAIL: '永田町ビル101号室', - SUBMISSION_STATUS: true, - SUBMISSION_DATE: '2021-01-01', - DETAIL_INFO: null, - REG_DT: new Date(), - UPT_DT: new Date(), - } return ( <> diff --git a/src/app/survey-sale/basic-info/page.tsx b/src/app/survey-sale/basic-info/page.tsx deleted file mode 100644 index 7359926..0000000 --- a/src/app/survey-sale/basic-info/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import BasicForm from '@/components/survey-sale/detail/my/basicForm' - -export default function page() { - return ( - <> - - - ) -} diff --git a/src/app/survey-sale/regist/page.tsx b/src/app/survey-sale/regist/page.tsx index cf18ff2..0dec827 100644 --- a/src/app/survey-sale/regist/page.tsx +++ b/src/app/survey-sale/regist/page.tsx @@ -1,5 +1,4 @@ -// import RegistForm from '@/components/survey-sale/temp/registForm' -import RegistForm from '@/components/survey-sale/RegistForm' +import RegistForm from '@/components/survey-sale/detail/RegistForm' export default function RegistPage() { return ( diff --git a/src/app/survey-sale/roof-info/page.tsx b/src/app/survey-sale/roof-info/page.tsx deleted file mode 100644 index 51797e6..0000000 --- a/src/app/survey-sale/roof-info/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import RoofInfoForm from '@/components/survey-sale/detail/my/roofInfoForm' - -export default function page() { - return ( - <> - - - ) -} \ No newline at end of file diff --git a/src/components/survey-sale/detail/BasicForm.tsx b/src/components/survey-sale/detail/BasicForm.tsx index bee4623..716930e 100644 --- a/src/components/survey-sale/detail/BasicForm.tsx +++ b/src/components/survey-sale/detail/BasicForm.tsx @@ -32,8 +32,8 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas type="text" className="input-frame" readOnly={mode === 'READ'} - value={basicInfo?.REPRESENTATIVE ?? ''} - onChange={(e) => setBasicInfo({ ...basicInfo, REPRESENTATIVE: e.target.value })} + value={basicInfo?.representative ?? ''} + onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })} />
@@ -42,8 +42,8 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas type="text" className="input-frame" readOnly={mode === 'READ'} - value={basicInfo?.STORE ?? ''} - onChange={(e) => setBasicInfo({ ...basicInfo, STORE: e.target.value })} + value={basicInfo?.store ?? ''} + onChange={(e) => setBasicInfo({ ...basicInfo, store: e.target.value })} />
@@ -52,8 +52,8 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas type="text" className="input-frame" readOnly={mode === 'READ'} - value={basicInfo?.CONSTRUCTION_POINT ?? ''} - onChange={(e) => setBasicInfo({ ...basicInfo, CONSTRUCTION_POINT: e.target.value })} + value={basicInfo?.constructionPoint ?? ''} + onChange={(e) => setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })} />
@@ -67,32 +67,32 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas - + ) : ( - + )}
{/* 건물명 */}
建物名
- +
{/* 고객명 */}
建物名
- +
郵便番号/都道府県
{/* 우편번호 */}
- +
{/* 도도부현 */}
- +
{/* 주소 */} diff --git a/src/components/survey-sale/detail/DataTable.tsx b/src/components/survey-sale/detail/DataTable.tsx index be8da34..1d06e2e 100644 --- a/src/components/survey-sale/detail/DataTable.tsx +++ b/src/components/survey-sale/detail/DataTable.tsx @@ -11,7 +11,6 @@ export default function DataTable() { const id = params.id const searchParams = useSearchParams() - const tab = searchParams.get('tab') const isTemp = searchParams.get('isTemporary') const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id)) @@ -20,8 +19,8 @@ export default function DataTable() { const { validateSurveyDetail } = useServey(Number(id)) useEffect(() => { - if (surveyDetail?.DETAIL_INFO) { - const validate = validateSurveyDetail(surveyDetail.DETAIL_INFO) + if (surveyDetail?.detailInfo) { + const validate = validateSurveyDetail(surveyDetail.detailInfo) if (validate.trim() !== '') { setIsTemporary(false) } @@ -48,25 +47,25 @@ export default function DataTable() { 仮保存 ) : ( - {surveyDetail?.ID} + {surveyDetail?.id} )} 登録日 - {surveyDetail?.REG_DT ? new Date(surveyDetail?.REG_DT).toLocaleString() : ''} + {surveyDetail?.regDt ? new Date(surveyDetail.regDt).toLocaleString() : ''} 更新日時 - {surveyDetail?.UPT_DT ? new Date(surveyDetail?.UPT_DT).toLocaleString() : ''} + {surveyDetail?.uptDt ? new Date(surveyDetail.uptDt).toLocaleString() : ''} 提出可否 - {surveyDetail?.SUBMISSION_STATUS && surveyDetail?.SUBMISSION_DATE ? ( + {surveyDetail?.submissionStatus && surveyDetail?.submissionDate ? ( <> {/* TODO: 제출한 판매점 ID 추가 필요 */} -
{new Date(surveyDetail.SUBMISSION_DATE).toLocaleString()}
-
{surveyDetail.STORE}
+
{new Date(surveyDetail.submissionDate).toLocaleString()}
+
{surveyDetail.store}
) : ( '-' diff --git a/src/components/survey-sale/detail/DetailForm.tsx b/src/components/survey-sale/detail/DetailForm.tsx index 92dbce4..387bb4f 100644 --- a/src/components/survey-sale/detail/DetailForm.tsx +++ b/src/components/survey-sale/detail/DetailForm.tsx @@ -6,55 +6,55 @@ import ButtonForm from './ButtonForm' import BasicForm from './BasicForm' import RoofForm from './RoofForm' const roofInfoForm: 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, + contractCapacity: null, + retailCompany: null, + supplementaryFacilities: null, + supplementaryFacilitiesEtc: null, + installationSystem: null, + installationSystemEtc: null, + constructionYear: null, + constructionYearEtc: null, + roofMaterial: null, + roofMaterialEtc: null, + roofShape: null, + roofShapeEtc: null, + roofSlope: null, + houseStructure: '1', + houseStructureEtc: null, + rafterMaterial: '1', + rafterMaterialEtc: null, + rafterSize: null, + rafterSizeEtc: null, + rafterPitch: null, + rafterPitchEtc: null, + rafterDirection: '1', + openFieldPlateKind: null, + openFieldPlateKindEtc: null, + openFieldPlateThickness: null, + leakTrace: false, + waterproofMaterial: null, + waterproofMaterialEtc: null, + insulationPresence: '1', + insulationPresenceEtc: null, + structureOrder: null, + structureOrderEtc: null, + installationAvailability: null, + installationAvailabilityEtc: null, + memo: null, } const basicInfoForm: SurveyBasicRequest = { - REPRESENTATIVE: '', - STORE: null, - CONSTRUCTION_POINT: null, - INVESTIGATION_DATE: new Date().toLocaleDateString('en-CA'), - BUILDING_NAME: null, - CUSTOMER_NAME: null, - POST_CODE: null, - ADDRESS: null, - ADDRESS_DETAIL: null, - SUBMISSION_STATUS: false, - SUBMISSION_DATE: null, + representative: '', + store: null, + constructionPoint: null, + investigationDate: new Date().toLocaleDateString('en-CA'), + buildingName: null, + customerName: null, + postCode: null, + address: null, + addressDetail: null, + submissionStatus: false, + submissionDate: null, } export default function DetailForm(props: { surveyInfo?: SurveyBasicInfo; mode?: Mode }) { @@ -62,20 +62,12 @@ export default function DetailForm(props: { surveyInfo?: SurveyBasicInfo; mode?: const [basicInfoData, setBasicInfoData] = useState(basicInfoForm) const [roofInfoData, setRoofInfoData] = useState(roofInfoForm) - // useEffect(() => { - // // setMode(props.surveyInfo ? 'EDIT' : 'CREATE') - // }, [props.surveyInfo]) - useEffect(() => { - console.log(props.surveyInfo) - }, [props.surveyInfo]) - - useEffect(() => { - if (props.surveyInfo && mode === 'EDIT') { - const { ID, UPT_DT, REG_DT, DETAIL_INFO, ...rest } = props.surveyInfo + if (props.surveyInfo && (mode === 'EDIT' || mode === 'READ')) { + const { id, uptDt, regDt, detailInfo, ...rest } = props.surveyInfo setBasicInfoData(rest) - if (DETAIL_INFO) { - const { ID, UPT_DT, REG_DT, BASIC_INFO_ID, ...rest } = DETAIL_INFO + if (detailInfo) { + const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo setRoofInfoData(rest) } } @@ -91,7 +83,6 @@ export default function DetailForm(props: { surveyInfo?: SurveyBasicInfo; mode?: return ( <>
- {/* {mode} */} {/* 기본정보 */} {/* 전기/지붕정보 */} diff --git a/src/components/survey-sale/RegistForm.tsx b/src/components/survey-sale/detail/RegistForm.tsx similarity index 94% rename from src/components/survey-sale/RegistForm.tsx rename to src/components/survey-sale/detail/RegistForm.tsx index b1cc240..4377713 100644 --- a/src/components/survey-sale/RegistForm.tsx +++ b/src/components/survey-sale/detail/RegistForm.tsx @@ -1,6 +1,6 @@ import { Mode } from '@/types/Survey' import { useSearchParams } from 'next/navigation' -import DetailForm from './detail/DetailForm' +import DetailForm from './DetailForm' import { useServey } from '@/hooks/useSurvey' import { useEffect, useState } from 'react' import { SurveyBasicInfo } from '@/types/Survey' diff --git a/src/components/survey-sale/detail/RoofForm.tsx b/src/components/survey-sale/detail/RoofForm.tsx index 1e92ae9..794bf21 100644 --- a/src/components/survey-sale/detail/RoofForm.tsx +++ b/src/components/survey-sale/detail/RoofForm.tsx @@ -1,8 +1,204 @@ import { useState } from 'react' -import { Mode, SurveyBasicInfo, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' -import { roof_material, supplementary_facilities } from './form/etcProcess/MultiCheckEtc' -import { selectBoxOptions } from './form/etcProcess/SelectBoxEtc' -import { radioEtcData } from './form/etcProcess/RadioEtc' +import { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' + +type RadioEtcKeys = 'houseStructure' | 'rafterMaterial' | 'waterproofMaterial' | 'insulationPresence' | 'rafterDirection' | 'leakTrace' +type SelectBoxKeys = + | 'installationSystem' + | 'constructionYear' + | 'roofShape' + | 'rafterPitch' + | 'rafterSize' + | 'openFieldPlateKind' + | 'structureOrder' + | 'installationAvailability' + +export const supplementary_facilities = [ + { id: 1, name: 'エコキュート' }, //에코큐트 + { id: 2, name: 'エネパーム' }, //에네팜 + { id: 3, name: '蓄電池システム' }, //축전지시스템 + { id: 4, name: '太陽光発電' }, //태양광발전 +] + +export const roof_material = [ + { id: 1, name: 'スレート' }, //슬레이트 + { id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글 + { id: 3, name: '瓦' }, //기와 + { id: 4, name: '金属屋根' }, //금속지붕 +] + +export const selectBoxOptions: Record = { + installationSystem: [ + { + id: 1, + name: '太陽光発電', //태양광발전 + }, + { + id: 2, + name: 'ハイブリッド蓄電システム', //하이브리드축전지시스템 + }, + { + id: 3, + name: '蓄電池システム', //축전지시스템 + }, + ], + constructionYear: [ + { + id: 1, + name: '新築', //신축 + }, + { + id: 2, + name: '既築', //기존 + }, + ], + roofShape: [ + { + id: 1, + name: '切妻', //박공지붕 + }, + { + id: 2, + name: '寄棟', //기동 + }, + { + id: 3, + name: '片流れ', //한쪽흐름 + }, + ], + rafterSize: [ + { + id: 1, + name: '幅35mm以上×高さ48mm以上', + }, + { + id: 2, + name: '幅36mm以上×高さ46mm以上', + }, + { + id: 3, + name: '幅37mm以上×高さ43mm以上', + }, + { + id: 4, + name: '幅38mm以上×高さ40mm以上', + }, + ], + rafterPitch: [ + { + id: 1, + name: '455mm以下', + }, + { + id: 2, + name: '500mm以下', + }, + { + id: 3, + name: '606mm以下', + }, + ], + openFieldPlateKind: [ + { + id: 1, + name: '構造用合板', //구조용합판 + }, + { + id: 2, + name: 'OSB', //OSB + }, + { + id: 3, + name: 'パーティクルボード', //파티클보드 + }, + { + id: 4, + name: '小幅板', //소판 + }, + ], + structureOrder: [ + { + id: 1, + name: '屋根材', //지붕재 + }, + { + id: 2, + name: '防水材', //방수재 + }, + { + id: 3, + name: '屋根の基礎', //지붕의기초 + }, + { + id: 4, + name: '垂木', //서까래 + }, + ], + installationAvailability: [ + { + id: 1, + name: '確認済み', //확인완료 + }, + { + id: 2, + name: '未確認', //미확인 + }, + ], +} + +export const radioEtcData: Record = { + houseStructure: [ + { + id: 1, + label: '木製', + }, + ], + rafterMaterial: [ + { + id: 1, + label: '木製', + }, + { + id: 2, + label: '強制', + }, + ], + waterproofMaterial: [ + { + id: 1, + label: 'アスファルト屋根940(22kg以上)', + }, + ], + insulationPresence: [ + { + id: 1, + label: 'なし', + }, + { + id: 2, + label: 'あり', + }, + ], + rafterDirection: [ + { + id: 1, + label: '垂直垂木', + }, + { + id: 2, + label: '水平垂木', + }, + ], + leakTrace: [ + { + id: 1, + label: 'あり', + }, + { + id: 2, + label: 'なし', + }, + ], +} export default function RoofForm(props: { roofInfo: SurveyDetailRequest | SurveyDetailInfo @@ -34,7 +230,7 @@ export default function RoofForm(props: { {/* 전기 계약 용량 */}
電気契約容量
- +
{mode === 'READ' && } {mode !== 'READ' && ( @@ -52,7 +248,7 @@ export default function RoofForm(props: {
{/* 전기 소매 회사사 */}
電気小売会社
- +
{/* 전기 부대 설비 */} @@ -69,19 +265,19 @@ export default function RoofForm(props: {
))}
- - + +
- +
@@ -104,7 +300,7 @@ export default function RoofForm(props: {
)} */}
設置希望システム
- + @@ -126,7 +322,7 @@ export default function RoofForm(props: { )} */} - + {/*
@@ -141,17 +337,17 @@ export default function RoofForm(props: {
{roof_material.map((item) => (
- +
))}
- - + +
- +
@@ -168,7 +364,7 @@ export default function RoofForm(props: { )} */} - +
@@ -178,46 +374,46 @@ export default function RoofForm(props: { {/* 지붕 경사도도 */}
屋根の斜面
- +
{/* 주택구조조 */}
住宅構造
- +
{/* 서까래 재질 */}
垂木材質
- +
{/* 서까래 크기 */}
垂木サイズ
- +
{/* 서까래 피치 */}
垂木サイズ
- +
{/* 서까래 방향 */}
垂木の方向
- +
{/* 노지판 종류류 */}
路地板の種類
- +
@@ -226,7 +422,7 @@ export default function RoofForm(props: { 路地板厚※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載
- + mm
@@ -234,28 +430,28 @@ export default function RoofForm(props: { {/* 누수 흔적 */}
水漏れの痕跡
- +
{/* 방수재 종류 */}
防水材の種類
- +
{/* 단열재 유무 */}
断熱材の有無
- +
{/* 지붕 구조의 순서 */}
屋根構造の順序
- +
{/* 지붕 제품명 설치 가능 여부 확인 */}
屋根製品名 設置可否確認
- +
{/* 메모 */} @@ -266,7 +462,7 @@ export default function RoofForm(props: { name="" id="" placeholder="TextArea Filed" - value={roofInfo?.MEMO ?? ''} + value={roofInfo?.memo ?? ''} readOnly={mode === 'READ'} >
@@ -280,7 +476,7 @@ export default function RoofForm(props: { const SelectedBox = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo }) => { const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo] - const etcValue = detailInfoData?.[`${column}_ETC` as keyof SurveyDetailInfo] + const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo] return ( <> @@ -296,18 +492,18 @@ const SelectedBox = ({ column, detailInfoData }: { column: string; detailInfoDat const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo | null }) => { let selectedId = detailInfoData?.[column as keyof SurveyDetailInfo] - if (column === 'LEAK_TRACE') { + if (column === 'leakTrace') { selectedId = Number(selectedId) if (!selectedId) selectedId = 2 } let etcValue = null - if (column !== 'RAFTER_DIRECTION') { - etcValue = detailInfoData?.[`${column}_ETC` as keyof SurveyDetailInfo] + if (column !== 'rafterDirection') { + etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo] } const etcChecked = etcValue !== null && etcValue !== undefined && etcValue !== '' - console.log('column: selectedId', column, selectedId) + // console.log('column: selectedId', column, selectedId) return ( <> {radioEtcData[column as keyof typeof radioEtcData].map((item) => ( @@ -316,10 +512,10 @@ const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoD ))} - {column !== 'RAFTER_DIRECTION' && column !== 'LEAK_TRACE' && column !== 'INSULATION_PRESENCE' && ( + {column !== 'rafterDirection' && column !== 'leakTrace' && column !== 'insulationPresence' && (
- - + +
)} {etcChecked && ( diff --git a/src/components/survey-sale/detail/form/etcProcess/MultiCheckEtc.tsx b/src/components/survey-sale/detail/form/etcProcess/MultiCheckEtc.tsx deleted file mode 100644 index 30a1ca7..0000000 --- a/src/components/survey-sale/detail/form/etcProcess/MultiCheckEtc.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { SurveyDetailRequest } from '@/types/Survey' -import { useEffect, useState } from 'react' - -export const supplementary_facilities = [ - { id: 1, name: 'エコキュート' }, //에코큐트 - { id: 2, name: 'エネパーム' }, //에네팜 - { id: 3, name: '蓄電池システム' }, //축전지시스템 - { id: 4, name: '太陽光発電' }, //태양광발전 -] - -export 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 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))) { - // 체크 해제 - 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 = makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')) - const currentSelected = value.length - if (!isOtherChecked && currentSelected >= 2) { - alert('Up to two roofing materials can be selected.') - return - } - } - - const newIsOtherChecked = !isOtherChecked - setIsOtherChecked(newIsOtherChecked) - setOtherValue('') - - setDetailInfoData({ - ...detailInfoData, - [`${column}_ETC`]: newIsOtherChecked ? '' : null, - }) - } - - const handleOtherInputChange = (e: React.ChangeEvent) => { - const value = e.target.value - setOtherValue(value) - setDetailInfoData({ - ...detailInfoData, - [`${column}_ETC`]: value, - }) - } - - return ( - <> - {column === 'SUPPLEMENTARY_FACILITIES' ? ( - <> -
- 電気袋設備※複数選択可能 -
- - ) : ( - <> -
- 屋根材※最大2個まで選択可能 -
- - )} -
- {selectList.map((item) => ( -
- handleCheckbox(item.id)} - /> - -
- ))} -
- - -
-
-
- -
- - ) -} diff --git a/src/components/survey-sale/detail/form/etcProcess/RadioEtc.tsx b/src/components/survey-sale/detail/form/etcProcess/RadioEtc.tsx deleted file mode 100644 index 29f3325..0000000 --- a/src/components/survey-sale/detail/form/etcProcess/RadioEtc.tsx +++ /dev/null @@ -1,175 +0,0 @@ -'use client' -import { useEffect, useState } from 'react' -import { SurveyDetailRequest } from '@/types/Survey' - -type RadioEtcKeys = 'HOUSE_STRUCTURE' | 'RAFTER_MATERIAL' | 'WATERPROOF_MATERIAL' | 'INSULATION_PRESENCE' | 'RAFTER_DIRECTION' | 'LEAK_TRACE' - -const translateJapanese: Record = { - HOUSE_STRUCTURE: '住宅構造', - RAFTER_MATERIAL: '垂木材質', - WATERPROOF_MATERIAL: '防水材の種類', - INSULATION_PRESENCE: '断熱材の有無', - RAFTER_DIRECTION: '垂木の方向', - LEAK_TRACE: '水漏れの痕跡', -} - -export const radioEtcData: Record = { - 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: 'あり', - }, - ], - RAFTER_DIRECTION: [ - { - id: 1, - label: '垂直垂木', - }, - { - id: 2, - label: '水平垂木', - }, - ], - LEAK_TRACE: [ - { - 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('') - - 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) => { - // const value = e.target.value - // if (column === 'INSULATION_PRESENCE') { - // setIsEtcSelected(value === '2') - // setDetailInfoData({ - // ...detailInfoData, - // [column]: value, - // }) - // } else if (value === 'etc') { - // setIsEtcSelected(true) - // setDetailInfoData({ - // ...detailInfoData, - // [column]: null, - // }) - // } else { - // setIsEtcSelected(false) - // setEtcValue('') - // setDetailInfoData({ - // ...detailInfoData, - // [column]: value, - // [`${column}_ETC`]: null, - // }) - // } - const value = e.target.value - const isSpecialCase = column === 'INSULATION_PRESENCE' - 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) setEtcValue('') - setDetailInfoData(updatedData) - } - - const handleEtcInputChange = (e: React.ChangeEvent) => { - const value = e.target.value - setEtcValue(value) - setDetailInfoData({ - ...detailInfoData, - [`${column}_ETC`]: value, - }) - } - - return ( -
-
{translateJapanese[column]}
- {radioEtcData[column].map((item) => ( -
- - -
- ))} - {column !== 'INSULATION_PRESENCE' && ( -
- - -
- )} -
- -
-
- ) -} diff --git a/src/components/survey-sale/detail/form/etcProcess/SelectBoxEtc.tsx b/src/components/survey-sale/detail/form/etcProcess/SelectBoxEtc.tsx deleted file mode 100644 index f509972..0000000 --- a/src/components/survey-sale/detail/form/etcProcess/SelectBoxEtc.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import type { SurveyDetailRequest } from '@/types/Survey' -import { useEffect, useState } from 'react' - -export type SelectBoxKeys = - | 'INSTALLATION_SYSTEM' - | 'CONSTRUCTION_YEAR' - | 'ROOF_SHAPE' - | 'RAFTER_PITCH' - | 'RAFTER_SIZE' - | 'OPEN_FIELD_PLATE_KIND' - | 'STRUCTURE_ORDER' - | 'INSTALLATION_AVAILABILITY' - -const font: Record = { - 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 = { - INSTALLATION_SYSTEM: '設置希望システム', - CONSTRUCTION_YEAR: '建築年数', - ROOF_SHAPE: '屋根の形状', - RAFTER_PITCH: '垂木傾斜', - RAFTER_SIZE: '垂木サイズ', - OPEN_FIELD_PLATE_KIND: '路地板の種類', - STRUCTURE_ORDER: '屋根構造の順序', - INSTALLATION_AVAILABILITY: '屋根製品名 設置可否確認', -} - -export const selectBoxOptions: Record = { - 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(() => { - if (detailInfoData[`${column}_ETC` as keyof SurveyDetailRequest]) { - setIsEtcSelected(true) - setEtcValue(detailInfoData[`${column}_ETC` as keyof SurveyDetailRequest] as string) - } - }, [detailInfoData]) - - const handleSelectChange = (e: React.ChangeEvent) => { - const value = e.target.value - const isSpecialCase = column === 'CONSTRUCTION_YEAR' || column === 'INSTALLATION_AVAILABILITY' - const isEtc = value === 'etc' - const isSpecialEtc = isSpecialCase && value === '2' - - const updatedData: typeof detailInfoData = { - ...detailInfoData, - [column]: isEtc ? null : value, - [`${column}_ETC`]: isEtc ? '' : null, - } - - // 건축연수 + 설치가능여부는 2번 선택 시 input 활성화 - if (isSpecialEtc) { - updatedData[column] = value - } - - setIsEtcSelected(isEtc || isSpecialEtc) - if (!isEtc) setEtcValue('') - setDetailInfoData(updatedData) - } - - const handleEtcInputChange = (e: React.ChangeEvent) => { - const value = e.target.value - setEtcValue(value) - setDetailInfoData({ - ...detailInfoData, - [`${column}_ETC`]: value, - }) - } - - return ( - <> -
-
{translateJapanese[column as keyof typeof translateJapanese]}
-
- -
-
- -
-
- - ) -} diff --git a/src/components/survey-sale/detail/my/basicForm.tsx b/src/components/survey-sale/detail/my/basicForm.tsx deleted file mode 100644 index 1f55838..0000000 --- a/src/components/survey-sale/detail/my/basicForm.tsx +++ /dev/null @@ -1,244 +0,0 @@ -'use client' - -import { useServey } from '@/hooks/useSurvey' -import { SurveyBasicRequest } from '@/types/Survey' -import { useRouter, useSearchParams } from 'next/navigation' -import { useState, useEffect } from 'react' -import { useSurveySaleTabState } from '@/store/surveySaleTabState' -import { usePopupController } from '@/store/popupController' -import { useAddressStore } from '@/store/addressStore' -import { useSessionStore } from '@/store/session' -// import { useUserType } from '@/hooks/useUserType' - -const defaultBasicInfoForm: SurveyBasicRequest = { - REPRESENTATIVE: '', - STORE: null, - CONSTRUCTION_POINT: null, - INVESTIGATION_DATE: new Date().toLocaleDateString('en-CA'), - BUILDING_NAME: null, - CUSTOMER_NAME: null, - POST_CODE: null, - ADDRESS: null, - ADDRESS_DETAIL: null, - SUBMISSION_STATUS: false, - SUBMISSION_DATE: null, -} - -const REQUIRED_FIELDS: (keyof SurveyBasicRequest)[] = ['REPRESENTATIVE', 'BUILDING_NAME', 'CUSTOMER_NAME'] - -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(defaultBasicInfoForm) - - const { addressData } = useAddressStore() - const { session } = useSessionStore() - - const popupController = usePopupController() - - useEffect(() => { - if (surveyDetail) { - const { ID, UPT_DT, REG_DT, DETAIL_INFO, ...rest } = surveyDetail - setBasicInfoData(rest) - } - if (addressData) { - setBasicInfoData({ - ...basicInfoData, - POST_CODE: addressData.post_code, - ADDRESS: addressData.address, - ADDRESS_DETAIL: addressData.address_detail, - }) - } - if (session?.isLoggedIn) { - setBasicInfoData((prev) => ({ - ...prev, - REPRESENTATIVE: session?.userId ?? '', - STORE: session?.storeNm ?? '', - CONSTRUCTION_POINT: session?.builderNo ?? '', - })) - } - setBasicInfoSelected() - }, [surveyDetail, addressData, session?.isLoggedIn, session?.userId, session?.storeNm, session?.builderNo]) - - 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 handleSave = async (isTemporary: boolean) => { - if (id) { - // updateSurvey(basicInfoData) - alert('保存しました。') - // router.push(`/survey-sale/${id}?tab=basic-info`) - } - if (isTemporary) { - // const saveId = await createSurvey(basicInfoData) - alert('一時保存されました。') - // router.push(`/survey-sale/${saveId}?tab=basic-info`) - } else { - if (validateSurvey(basicInfoData)) { - // const saveId = await createSurvey(basicInfoData) - alert('保存しました。') - // router.push(`/survey-sale/${saveId}?tab=basic-info`) - } - } - } - - if (isCreatingSurvey || isUpdatingSurvey) { - return
Loading...
- } - - return ( - <> -
-
-
-
担当者名
- handleChange('REPRESENTATIVE', e.target.value)} - /> -
- {(session?.role === 'Builder' || session?.role?.includes('Admin')) && ( - <> -
-
販売店
- handleChange('STORE', e.target.value)} - /> -
- - )} - {(session?.role === 'Partner' || session?.role === 'Builder') && ( -
-
施工店
- handleChange('CONSTRUCTION_POINT', e.target.value)} - /> -
- )} -
-
- -
-
-
-
現地調査日
-
- - handleChange('INVESTIGATION_DATE', e.target.value)} - /> -
-
-
-
建物名
- handleChange('BUILDING_NAME', e.target.value)} - /> -
-
-
顧客名
- handleChange('CUSTOMER_NAME', e.target.value)} - /> -
-
-
建物の住所
-
-
- -
-
- -
-
-
- -
-
-
-
市区町村名, 以後の住所
- handleChange('ADDRESS_DETAIL', e.target.value)} - /> -
-
-
-
- -
-
- -
-
- -
-
-
- - ) -} diff --git a/src/components/survey-sale/detail/my/detailButton.tsx b/src/components/survey-sale/detail/my/detailButton.tsx deleted file mode 100644 index 3a871bf..0000000 --- a/src/components/survey-sale/detail/my/detailButton.tsx +++ /dev/null @@ -1,119 +0,0 @@ -'use client' -import { useRouter, useSearchParams } from 'next/navigation' -import { useServey } from '@/hooks/useSurvey' -import { useSessionStore } from '@/store/session' -import { SurveyBasicInfo } from '@/types/Survey' -import { useState } from 'react' - -export default function DetailButton({ surveyDetail }: { surveyDetail: SurveyBasicInfo | null }) { - const router = useRouter() - const { session } = useSessionStore() - const { submitSurvey, deleteSurvey } = useServey(surveyDetail?.ID ?? 0) - - const searchParams = useSearchParams() - const isTemp = searchParams.get('isTemporary') - const [isTemporary, setIsTemporary] = useState(isTemp === 'true') - - const checkRole = () => { - switch (session?.role) { - case 'T01': - return session?.userNm === surveyDetail?.REPRESENTATIVE ? true : false - case 'Admin': - return session?.storeNm === surveyDetail?.STORE ? true : false - case 'Admin_Sub': - return session?.storeNm === surveyDetail?.STORE ? true : false - case 'Builder': - return session?.builderNo === surveyDetail?.CONSTRUCTION_POINT ? true : false - case 'Partner': - return session?.builderNo === surveyDetail?.CONSTRUCTION_POINT ? true : false - default: - return '' - } - } - - const handleSubmit = async () => { - const result = checkRole() - if (result) { - if (isTemporary) { - alert('一時保存されたデータは提出できません。') - return - } - window.neoConfirm( - '提出しますか??', - async () => { - if (surveyDetail?.ID) { - // TODO: 제출 페이지 추가 - alert('SUBMIT POPUP!!!!!!!!!!!') - await submitSurvey() - } - }, - () => null, - ) - } - } - const handleUpdate = () => { - const result = checkRole() - if (result) { - // router.push(`/survey-sale/basic-info?id=${surveyDetail?.ID}&isTemp=${isTemporary}`) - router.push(`/survey-sale/regist?id=${surveyDetail?.ID}`) - } else { - alert('担当者のみ修正可能です。') - } - } - const handleDelete = async () => { - window.neoConfirm( - '削除しますか?', - async () => { - if (surveyDetail?.ID) { - if (session.userNm === surveyDetail?.REPRESENTATIVE) { - await deleteSurvey() - alert('削除されました。') - router.push('/survey-sale') - } else { - alert('担当者のみ削除可能です。') - } - } - }, - () => null, - ) - } - - const isSubmitter = session?.storeNm === surveyDetail?.STORE && session?.builderNo === surveyDetail?.CONSTRUCTION_POINT - - return ( -
-
- -
- {isSubmitter && surveyDetail?.SUBMISSION_STATUS ? ( - <> - ) : ( - <> - {isTemporary || surveyDetail?.SUBMISSION_STATUS ? ( - <> - ) : ( - <> -
- -
- - )} -
- -
-
- -
- - )} -
- ) -} diff --git a/src/components/survey-sale/detail/my/roofDetailForm.tsx b/src/components/survey-sale/detail/my/roofDetailForm.tsx deleted file mode 100644 index 6b8bbb5..0000000 --- a/src/components/survey-sale/detail/my/roofDetailForm.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import { SurveyBasicInfo, SurveyDetailInfo } from '@/types/Survey' -import DetailButton from './detailButton' -import { roof_material, supplementary_facilities } from '../form/etcProcess/MultiCheckEtc' -import { selectBoxOptions } from '../form/etcProcess/SelectBoxEtc' -import { radioEtcData } from '../form/etcProcess/RadioEtc' - -export default function RoofDetailForm({ - surveyDetail, - isLoadingSurveyDetail, -}: { - surveyDetail: SurveyBasicInfo | null - isLoadingSurveyDetail: boolean -}) { - console.log(surveyDetail) - - const makeNumArr = (value: string) => { - return value - .split(',') - .map((v) => v.trim()) - .filter((v) => v.length > 0) - } - - if (isLoadingSurveyDetail) { - return
Loading...
- } - return ( - <> -
-
-
- {/* 전기 계약 용량 */} -
電気契約容量
- -
- {/* 전기 소매 회사 */} -
-
電気小売会社
- -
- {/* 전기 부대 설비 */} -
-
電気附属設備
-
- {supplementary_facilities.map((item) => ( -
- - -
- ))} -
- - -
-
-
- -
-
- {/* 설치 희망 시스템 */} -
-
設置希望システム
- -
- {/* 건축 연수 */} -
-
建築年数
- -
- {/* 지붕재 */} -
-
屋根材
-
- {roof_material.map((item) => ( -
- - -
- ))} -
- - -
-
-
- -
-
- {/* 지붕 모양 */} -
-
屋根の形状
- -
- {/* 지붕 경사도 */} -
-
屋根の斜面
-
- - -
-
- {/* 주택 구조 */} -
-
住宅構造
- -
- {/* 서까래 재질 */} -
-
垂木の材質
- -
- {/* 서까래 크기 */} -
-
垂木の大きさ
- -
- {/* 서까래 피치 */} -
-
垂木のピッチ
- -
- {/* 서까래 방향 */} -
-
垂木の方向
- -
- {/* 노지판 종류 */} -
-
路地板の種類
- -
- {/* 노지판 두께 */} -
-
路地板厚
-
- - mm -
-
- {/* 누수 흔적 */} -
-
水漏れの痕跡
- -
- {/* 방수재 종류 */} -
-
防水材の種類
- -
- {/* 단열재 유무 */} -
-
断熱材の有無
- -
- {/* 구조 순서 */} -
-
屋根構造の順序
- -
- {/* 설치 가능 여부 */} -
-
設置可能な場合
- -
- {/* 메모 */} -
-
メモ
-
- -
-
-
-
-
- -
-
- -
-
- -
-
-
- - ) -} diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index 468f01d..b1ac703 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -7,33 +7,32 @@ import { useRouter } from 'next/navigation' import SearchForm from './SearchForm' import { useSurveyFilterStore } from '@/store/surveyFilterStore' import { useSessionStore } from '@/store/session' +import { SurveyBasicInfo } from '@/types/Survey' export default function ListTable() { const router = useRouter() - const { surveyList, isLoadingSurveyList, surveyListCount } = useServey() + const { surveyList, isLoadingSurveyList } = useServey() const { offset, setOffset } = useSurveyFilterStore() - const [heldSurveyList, setHeldSurveyList] = useState([]) + const [heldSurveyList, setHeldSurveyList] = useState([]) const [hasMore, setHasMore] = useState(false) const { session } = useSessionStore() useEffect(() => { - if (surveyList) { - if (offset === 0) { - setHeldSurveyList(surveyList) + if (!session.isLoggedIn || !('data' in surveyList)) return + if ('count' in surveyList && surveyList.count > 0) { + if (offset > 0) { + setHeldSurveyList((prev) => [...prev, ...surveyList.data]) } else { - setHeldSurveyList(prev => [...prev, ...surveyList]) + setHeldSurveyList(surveyList.data) } - setHasMore(surveyListCount > offset + 10) + setHasMore(surveyList.count > offset + 10) } else { setHeldSurveyList([]) setHasMore(false) } - }, [surveyList, surveyListCount, offset]) - - console.log('surveyList:: ', surveyList) - console.log('heldSurveyList:: ', heldSurveyList) + }, [surveyList, offset, session]) const handleDetailClick = (id: number) => { router.push(`/survey-sale/${id}`) @@ -48,22 +47,22 @@ export default function ListTable() { return ( <> - + {heldSurveyList.length > 0 ? (
    {heldSurveyList.map((survey) => ( -
  • handleDetailClick(survey.ID)}> +
  • handleDetailClick(survey.id)}>
    -
    {survey.ID}
    -
    {survey.INVESTIGATION_DATE}
    +
    {survey.id}
    +
    {survey.investigationDate}
    -
    {survey.BUILDING_NAME}
    -
    {survey.CUSTOMER_NAME}
    +
    {survey.buildingName}
    +
    {survey.customerName}
    -
    {survey.REPRESENTATIVE}
    -
    {new Date(survey.UPT_DT).toLocaleString()}
    +
    {survey.representative}
    +
    {new Date(survey.uptDt).toLocaleString()}
  • diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 16a259d..327a85c 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -4,7 +4,7 @@ import { SEARCH_OPTIONS, SEARCH_OPTIONS_ENUM, SEARCH_OPTIONS_PARTNERS, useSurvey import { useRouter } from 'next/navigation' import { useState } from 'react' -export default function SearchForm({ onItemsInit, memberRole, userId }: { onItemsInit: () => void; memberRole: string; userId: string }) { +export default function SearchForm({ memberRole, userId }: { memberRole: string; userId: string }) { const router = useRouter() const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore() const [searchKeyword, setSearchKeyword] = useState(keyword) diff --git a/src/components/survey-sale/temp/basicRegist.tsx b/src/components/survey-sale/temp/basicRegist.tsx deleted file mode 100644 index c5ddaf1..0000000 --- a/src/components/survey-sale/temp/basicRegist.tsx +++ /dev/null @@ -1,153 +0,0 @@ -'use client' - -import { SurveyBasicRequest, SurveyRegistRequest } from '@/types/Survey' -import { useEffect } from 'react' -import { usePopupController } from '@/store/popupController' -import { useAddressStore } from '@/store/addressStore' -import { useSessionStore } from '@/store/session' - -export default function BasicRegist({ - basicInfoData, - setBasicInfoData, -}: { - basicInfoData: SurveyBasicRequest - setBasicInfoData: (data: SurveyBasicRequest) => void -}) { - const { addressData } = useAddressStore() - const { session } = useSessionStore() - - const popupController = usePopupController() - - useEffect(() => { - if (addressData) { - setBasicInfoData({ - ...basicInfoData, - POST_CODE: addressData.post_code, - ADDRESS: addressData.address, - ADDRESS_DETAIL: addressData.address_detail, - }) - } - }, [addressData]) - - const handleChange = (key: keyof SurveyRegistRequest, value: string) => { - setBasicInfoData({ ...basicInfoData, [key]: value }) - } - - return ( - <> -
    -
    -
    -
    担当者名
    - -
    - {(session?.role === 'Builder' || session?.role?.includes('Admin')) && ( - <> -
    -
    販売店
    - -
    - - )} - {(session?.role === 'Partner' || session?.role === 'Builder') && ( -
    -
    施工店
    - -
    - )} -
    -
    - -
    -
    -
    -
    現地調査日
    -
    - - handleChange('INVESTIGATION_DATE', e.target.value)} - /> -
    -
    -
    -
    建物名
    - handleChange('BUILDING_NAME', e.target.value)} - /> -
    -
    -
    顧客名
    - handleChange('CUSTOMER_NAME', e.target.value)} - /> -
    -
    -
    建物の住所
    -
    -
    - -
    -
    - handleChange('ADDRESS', e.target.value)} - /> -
    -
    -
    - -
    -
    -
    -
    市区町村名, 以後の住所
    - handleChange('ADDRESS_DETAIL', e.target.value)} - /> -
    -
    -
    - - ) -} diff --git a/src/components/survey-sale/temp/formButton.tsx b/src/components/survey-sale/temp/formButton.tsx deleted file mode 100644 index 7b41003..0000000 --- a/src/components/survey-sale/temp/formButton.tsx +++ /dev/null @@ -1,92 +0,0 @@ -'use client' - -import { SurveyBasicRequest, SurveyRegistRequest } from '@/types/Survey' -import { SurveyDetailRequest } from '@/types/Survey' -import { useRouter } from 'next/navigation' -import { useServey } from '@/hooks/useSurvey' - -export default function FormButton({ - surveyData, - idParam, -}: { - surveyData: { basic: SurveyBasicRequest; roof: SurveyDetailRequest } - idParam: string | null -}) { - const router = useRouter() - const { validateSurveyDetail, createSurvey, updateSurvey } = useServey(Number(idParam)) - - const saveData = { - ...surveyData.basic, - DETAIL_INFO: surveyData.roof, - } - - const focusInput = (input: keyof SurveyRegistRequest) => { - const inputElement = document.getElementById(input) - if (inputElement) { - inputElement.focus() - } - } - - const handleSave = (isTemporary: boolean) => { - const emptyField = validateSurveyDetail(saveData.DETAIL_INFO) - if (!isTemporary) { - saveProcess(emptyField) - } else { - temporarySaveProcess() - } - } - const saveProcess = async (emptyField: string) => { - if (emptyField.trim() === '') { - if (idParam) { - // 매물 수정 (저장) - updateSurvey(saveData) - router.push(`/survey-sale/${idParam}`) - } else { - // 매물 생성 (저장) - const id = await createSurvey(saveData) - router.push(`/survey-sale/${id}`) - } - alert('保存されました。') - } else { - alert(emptyField + ' 項目が空です。') - focusInput(emptyField as keyof SurveyRegistRequest) - } - } - - const temporarySaveProcess = async () => { - if (idParam) { - // 매물 수정 (임시저장) - updateSurvey(saveData) - router.push(`/survey-sale/${idParam}?isTemporary=true`) - } else { - // 매물 생성 (임시저장) - const id = await createSurvey(saveData) - router.push(`/survey-sale/${id}?isTemporary=true`) - } - alert('一時保存されました。') - } - - return ( - <> -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    - - ) -} diff --git a/src/components/survey-sale/temp/registForm.tsx b/src/components/survey-sale/temp/registForm.tsx deleted file mode 100644 index 42e7267..0000000 --- a/src/components/survey-sale/temp/registForm.tsx +++ /dev/null @@ -1,105 +0,0 @@ -'use client' - -import { SurveyBasicRequest, SurveyDetailRequest, SurveyRegistRequest } from '@/types/Survey' -import FormButton from './formButton' -import { useEffect, useState } from 'react' -import BasicRegist from './basicRegist' -import RoofRegist from './roofRegist' -import { useSessionStore } from '@/store/session' -import { useSearchParams } from 'next/navigation' -import { useServey } from '@/hooks/useSurvey' - -const roofInfoForm: 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, -} - -const basicInfoForm: SurveyBasicRequest = { - REPRESENTATIVE: '', - STORE: null, - CONSTRUCTION_POINT: null, - INVESTIGATION_DATE: new Date().toLocaleDateString('en-CA'), - BUILDING_NAME: null, - CUSTOMER_NAME: null, - POST_CODE: null, - ADDRESS: null, - ADDRESS_DETAIL: null, - SUBMISSION_STATUS: false, - SUBMISSION_DATE: null, -} - -export default function RegistForm() { - const searchParams = useSearchParams() - const id = searchParams.get('id') - - const { session } = useSessionStore() - const { surveyDetail } = useServey(Number(id)) - - const [basicInfoData, setBasicInfoData] = useState(basicInfoForm) - const [roofInfoData, setRoofInfoData] = useState(roofInfoForm) - - useEffect(() => { - if (session) { - setBasicInfoData({ - ...basicInfoForm, - REPRESENTATIVE: session.userNm ?? '', - STORE: session.role === 'T01' ? '' : session.storeNm ?? '', - CONSTRUCTION_POINT: session.builderNo ?? '', - }) - } - if (id && surveyDetail) { - const { ID, UPT_DT, REG_DT, DETAIL_INFO, ...rest } = surveyDetail - setBasicInfoData(rest) - if (surveyDetail?.DETAIL_INFO) { - const { ID, UPT_DT, REG_DT, BASIC_INFO_ID, ...rest } = surveyDetail.DETAIL_INFO - setRoofInfoData(rest) - } - } - }, [session, surveyDetail]) - - const surveyData = { - basic: basicInfoData, - roof: roofInfoData, - } - - return ( - <> - - - - - ) -} diff --git a/src/components/survey-sale/temp/roofRegist.tsx b/src/components/survey-sale/temp/roofRegist.tsx deleted file mode 100644 index 6c5492a..0000000 --- a/src/components/survey-sale/temp/roofRegist.tsx +++ /dev/null @@ -1,284 +0,0 @@ -'use client' - -import { useSurveySaleTabState } from '@/store/surveySaleTabState' - -import { SurveyBasicInfo, SurveyDetailRequest } from '@/types/Survey' -import { useEffect } from 'react' -import MultiCheckEtc from '../detail/form/etcProcess/MultiCheckEtc' -import SelectBoxEtc from '../detail/form/etcProcess/SelectBoxEtc' -import RadioEtc from '../detail/form/etcProcess/RadioEtc' - -export default function RoofRegist({ - roofInfoData, - setRoofInfoData, -}: { - roofInfoData: SurveyDetailRequest - setRoofInfoData: (data: SurveyDetailRequest) => void -}) { - const { setRoofInfoSelected } = useSurveySaleTabState() - - useEffect(() => { - setRoofInfoSelected() - }, []) - - const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => { - if (key === 'ROOF_SLOPE' || key === 'OPEN_FIELD_PLATE_THICKNESS') { - 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 - } - } - } - setRoofInfoData({ ...roofInfoData, [key]: value.toString() }) - } - - const handleTextInput = (key: keyof SurveyDetailRequest, value: string) => { - setRoofInfoData({ ...roofInfoData, [key]: value || null }) - } - - const handleBooleanInput = (key: keyof SurveyDetailRequest, value: boolean) => { - setRoofInfoData({ ...roofInfoData, [key]: value }) - } - - const handleUnitInput = (value: string) => { - const numericValue = roofInfoData.CONTRACT_CAPACITY?.replace(/[^0-9.]/g, '') || '' - setRoofInfoData({ - ...roofInfoData, - CONTRACT_CAPACITY: numericValue ? `${numericValue} ${value}` : value, - }) - } - - // const handleSave = async () => { - // if (id) { - // const emptyField = validateSurveyDetail(roofInfoData) - // if (emptyField.trim() === '') { - // const updatedBasicInfoData = { - // DETAIL_INFO: roofInfoData, - // } - // try { - // createSurveyDetail({ - // surveyId: Number(id), - // surveyDetail: updatedBasicInfoData, - // }) - // alert('調査物件を保存しました。') - // } catch (error) { - // alert(error) - // throw new Error('failed to create survey detail: ' + error) - // } - // router.push(`/survey-sale`) - // } else { - // alert(emptyField + ' は必須項目です。') - // focusOnInput(emptyField) - // } - // } else { - // alert('基本情報を作成した後、屋根情報を作成することができます。') - // } - // } - // const focusOnInput = (field: string) => { - // const input = document.getElementById(field) - // if (input) { - // input.focus() - // } - // } - return ( - <> -
    -
    電気関係
    -
    -
    - {/* 전기계약 용량 - contract_capacity */} -
    電気契約容量
    -
    - handleNumberInput('CONTRACT_CAPACITY', e.target.value)} - /> -
    -
    - -
    -
    - {/* 전기 소매 회사 - retail_company */} -
    -
    電気小売会社
    - handleTextInput('RETAIL_COMPANY', e.target.value)} - /> -
    - {/* 전기 부대 설비 - supplementary_facilities */} -
    - -
    - {/* 설치 희망 시스템 - installation_system */} - -
    -
    - -
    -
    屋根関係
    -
    - {/* 건축 연수 - construction_year */} - - {/* 지붕재 - roof_material */} -
    - -
    - {/* 지붕 모양 - roof_shape */} - - {/* 지붕 경사도 - roof_slope */} -
    -
    屋根の斜面
    -
    - handleNumberInput('ROOF_SLOPE', e.target.value)} - /> - -
    -
    - {/* 주택 구조 - house_structure */} - - {/* 서까래 재질 - rafter_material */} - - {/* 서까래 크기 - rafter_size */} - - {/* 서까래 피치 - rafter_pitch */} - - {/* 서까래 방향 - rafter_direction */} -
    -
    垂木の方向
    -
    -
    - handleNumberInput('RAFTER_DIRECTION', Number(e.target.value))} - checked={roofInfoData.RAFTER_DIRECTION === '1'} - /> - -
    -
    - handleNumberInput('RAFTER_DIRECTION', Number(e.target.value))} - checked={roofInfoData.RAFTER_DIRECTION === '2'} - /> - -
    -
    -
    - {/* 노지판 종류 - open_field_plate_kind */} - - {/* 노지판 두께 - open_field_plate_thickness */} -
    -
    - 路地板厚※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載 -
    -
    - handleNumberInput('OPEN_FIELD_PLATE_THICKNESS', e.target.value)} - /> - mm -
    -
    - {/* 누수 흔적 - leak_trace */} -
    -
    水漏れの痕跡
    -
    -
    - handleBooleanInput('LEAK_TRACE', true)} - /> - -
    -
    - handleBooleanInput('LEAK_TRACE', false)} - /> - -
    -
    -
    - {/* 방수재 종류 - waterproof_material */} - - {/* 단열재 유무 - insulation_presence */} - - {/* 노지판 종류 - open_field_plate_kind */} - - {/* 설치 가능 여부 - installation_availability */} - - {/* 메모 - memo */} -
    -
    メモ
    -
    - -
    -
    - -
    -
    -
    -
    - - ) -} diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index a047706..d07cddf 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -1,43 +1,39 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import type { - SurveyBasicInfo, - SurveyDetailInfo, - SurveyDetailRequest, - SurveyDetailCoverRequest, - SurveyRegistRequest, -} from '@/types/Survey' +import type { SurveyBasicInfo, SurveyDetailInfo, SurveyDetailRequest, SurveyDetailCoverRequest, SurveyRegistRequest } from '@/types/Survey' import { axiosInstance } from '@/libs/axios' import { useSurveyFilterStore } from '@/store/surveyFilterStore' import { queryStringFormatter } from '@/utils/common-utils' import { useSessionStore } from '@/store/session' +import { useMemo } from 'react' +import { AxiosResponse } from 'axios' const requiredFields = [ { - field: 'INSTALLATION_SYSTEM', + field: 'installationSystem', name: '設置希望システム', }, { - field: 'CONSTRUCTION_YEAR', + field: 'constructionYear', name: '建築年数', }, { - field: 'RAFTER_SIZE', + field: 'rafterSize', name: '垂木サイズ', }, { - field: 'RAFTER_PITCH', + field: 'rafterPitch', name: '垂木傾斜', }, { - field: 'WATERPROOF_MATERIAL', + field: 'waterproofMaterial', name: '防水材', }, { - field: 'INSULATION_PRESENCE', + field: 'insulationPresence', name: '断熱材有無', }, { - field: 'STRUCTURE_ORDER', + field: 'structureOrder', name: '屋根構造の順序', }, ] @@ -60,9 +56,8 @@ type ZipCode = { } export function useServey(id?: number): { - surveyList: SurveyBasicInfo[] | [] + surveyList: { data: SurveyBasicInfo[]; count: number } | {} surveyDetail: SurveyBasicInfo | null - surveyListCount: number isLoadingSurveyList: boolean isLoadingSurveyDetail: boolean isCreatingSurvey: boolean @@ -75,15 +70,20 @@ export function useServey(id?: number): { submitSurvey: () => void validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string getZipCode: (zipCode: string) => Promise + refetchSurveyList: () => void } { const queryClient = useQueryClient() const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore() const { session } = useSessionStore() - const { data: surveyList, isLoading: isLoadingSurveyList } = useQuery({ + const { + data, + isLoading: isLoadingSurveyList, + refetch: refetchSurveyList, + } = useQuery({ queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, session?.storeNm, session?.builderNo, session?.role], queryFn: async () => { - const resp = await axiosInstance(null).get('/api/survey-sales', { + const resp = await axiosInstance(null).get<{ data: SurveyBasicInfo[]; count: number }>('/api/survey-sales', { params: { keyword, searchOption, @@ -97,7 +97,15 @@ export function useServey(id?: number): { }) return resp.data }, + enabled: session?.isLoggedIn, }) + const surveyData = useMemo(() => { + if (!data) return {} + return { + data: data.data, + count: data.count, + } + }, [data]) const { data: surveyDetail, isLoading: isLoadingSurveyDetail } = useQuery({ queryKey: ['survey', id], @@ -110,28 +118,10 @@ export function useServey(id?: number): { enabled: id !== undefined, }) - const { data: surveyListCount } = useQuery({ - queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, session?.builderNo, session?.storeNm, session?.role], - queryFn: async () => { - const resp = await axiosInstance(null).get('/api/survey-sales', { - params: { - keyword, - searchOption, - isMySurvey, - sort, - builderNo: session?.builderNo, - store: session?.storeNm, - role: session?.role, - }, - }) - return resp.data - }, - }) - const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ mutationFn: async (survey: SurveyRegistRequest) => { const resp = await axiosInstance(null).post('/api/survey-sales', survey) - return resp.data.ID ?? 0 + return resp.data.id ?? 0 }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['survey', 'list'] }) @@ -190,7 +180,7 @@ export function useServey(id?: number): { }) const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => { - const etcFields = ['INSTALLATION_SYSTEM', 'CONSTRUCTION_YEAR', 'RAFTER_SIZE', 'RAFTER_PITCH', 'WATERPROOF_MATERIAL', 'STRUCTURE_ORDER'] as const + const etcFields = ['installationSystem', 'constructionYear', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const const emptyField = requiredFields.find((field) => { if (etcFields.includes(field.field as (typeof etcFields)[number])) { @@ -202,9 +192,9 @@ export function useServey(id?: number): { } }) - const contractCapacity = surveyDetail.CONTRACT_CAPACITY + const contractCapacity = surveyDetail.contractCapacity if (contractCapacity && contractCapacity.trim() !== '' && contractCapacity.split(' ')?.length === 1) { - return 'CONTRACT_CAPACITY_UNIT' + return 'contractCapacityUnit' } return emptyField?.name || '' @@ -223,9 +213,8 @@ export function useServey(id?: number): { } return { - surveyList: surveyList || [], - surveyDetail: surveyDetail || null, - surveyListCount: surveyListCount || 0, + surveyList: surveyData, + surveyDetail: surveyDetail as SurveyBasicInfo | null, isLoadingSurveyList, isLoadingSurveyDetail, isCreatingSurvey, @@ -238,5 +227,6 @@ export function useServey(id?: number): { submitSurvey, validateSurveyDetail, getZipCode, + refetchSurveyList, } } diff --git a/src/libs/axios.ts b/src/libs/axios.ts index 8718318..d973f9d 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -21,7 +21,10 @@ export const axiosInstance = (url: string | null | undefined) => { ) instance.interceptors.response.use( - (response) => transferResponse(response), + (response) => { + response.data = transferResponse(response) + return response + }, (error) => { // 에러 처리 로직 return Promise.reject(error) @@ -52,7 +55,7 @@ export const axiosInstance = (url: string | null | undefined) => { // ) // response데이터가 array, object에 따라 분기하여 키 변환 -const transferResponse = (response: any) => { +export const transferResponse = (response: any) => { if (!response.data) return response.data // 배열인 경우 각 객체의 키를 변환 @@ -80,7 +83,11 @@ const transformObjectKeys = (obj: any): any => { return obj } -// snake case to camel case -const snakeToCamel = (str: string): string => { - return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', '')) + +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('_', '')) } diff --git a/src/types/Survey.ts b/src/types/Survey.ts index 9030f21..36e7aa5 100644 --- a/src/types/Survey.ts +++ b/src/types/Survey.ts @@ -1,132 +1,132 @@ export type SurveyBasicInfo = { - ID: number - REPRESENTATIVE: string - STORE: string | null - CONSTRUCTION_POINT: string | null - INVESTIGATION_DATE: string | null - BUILDING_NAME: string | null - CUSTOMER_NAME: string | null - POST_CODE: string | null - ADDRESS: string | null - ADDRESS_DETAIL: string | null - SUBMISSION_STATUS: boolean - SUBMISSION_DATE: string | null - DETAIL_INFO: SurveyDetailInfo | null - REG_DT: Date - UPT_DT: Date + id: number + representative: string + store: string | null + constructionPoint: string | null + investigationDate: string | null + buildingName: string | null + customerName: string | null + postCode: string | null + address: string | null + addressDetail: string | null + submissionStatus: boolean + submissionDate: string | null + detailInfo: SurveyDetailInfo | null + regDt: Date + uptDt: Date } export type SurveyDetailInfo = { - ID: number - BASIC_INFO_ID: number - CONTRACT_CAPACITY: string | null - RETAIL_COMPANY: string | null - SUPPLEMENTARY_FACILITIES: string | null // number 배열 - SUPPLEMENTARY_FACILITIES_ETC: string | null - INSTALLATION_SYSTEM: string | null - INSTALLATION_SYSTEM_ETC: string | null - CONSTRUCTION_YEAR: string | null - CONSTRUCTION_YEAR_ETC: string | null - ROOF_MATERIAL: string | null // number 배열 - ROOF_MATERIAL_ETC: string | null - ROOF_SHAPE: string | null - ROOF_SHAPE_ETC: string | null - ROOF_SLOPE: string | null - HOUSE_STRUCTURE: string | null - HOUSE_STRUCTURE_ETC: string | null - RAFTER_MATERIAL: string | null - RAFTER_MATERIAL_ETC: string | null - RAFTER_SIZE: string | null - RAFTER_SIZE_ETC: string | null - RAFTER_PITCH: string | null - RAFTER_PITCH_ETC: string | null - RAFTER_DIRECTION: string | null - OPEN_FIELD_PLATE_KIND: string | null - OPEN_FIELD_PLATE_KIND_ETC: string | null - OPEN_FIELD_PLATE_THICKNESS: string | null - LEAK_TRACE: boolean | null - WATERPROOF_MATERIAL: string | null - WATERPROOF_MATERIAL_ETC: string | null - INSULATION_PRESENCE: string | null - INSULATION_PRESENCE_ETC: string | null - STRUCTURE_ORDER: string | null - STRUCTURE_ORDER_ETC: string | null - INSTALLATION_AVAILABILITY: string | null - INSTALLATION_AVAILABILITY_ETC: string | null - MEMO: string | null - REG_DT: Date - UPT_DT: Date + id: number + basicInfoId: number + contractCapacity: string | null + retailCompany: string | null + supplementaryFacilities: string | null // number 배열 + supplementaryFacilitiesEtc: string | null + installationSystem: string | null + installationSystemEtc: string | null + constructionYear: string | null + constructionYearEtc: string | null + roofMaterial: string | null // number 배열 + roofMaterialEtc: string | null + roofShape: string | null + roofShapeEtc: string | null + roofSlope: string | null + houseStructure: string | null + houseStructureEtc: string | null + rafterMaterial: string | null + rafterMaterialEtc: string | null + rafterSize: string | null + rafterSizeEtc: string | null + rafterPitch: string | null + rafterPitchEtc: string | null + rafterDirection: string | null + openFieldPlateKind: string | null + openFieldPlateKindEtc: string | null + openFieldPlateThickness: string | null + leakTrace: boolean | null + waterproofMaterial: string | null + waterproofMaterialEtc: string | null + insulationPresence: string | null + insulationPresenceEtc: string | null + structureOrder: string | null + structureOrderEtc: string | null + installationAvailability: string | null + installationAvailabilityEtc: string | null + memo: string | null + regDt: Date + uptDt: Date } export type SurveyBasicRequest = { - REPRESENTATIVE: string - STORE: string | null - CONSTRUCTION_POINT: string | null - INVESTIGATION_DATE: string | null - BUILDING_NAME: string | null - CUSTOMER_NAME: string | null - POST_CODE: string | null - ADDRESS: string | null - ADDRESS_DETAIL: string | null - SUBMISSION_STATUS: boolean - SUBMISSION_DATE: string | null + representative: string + store: string | null + constructionPoint: string | null + investigationDate: string | null + buildingName: string | null + customerName: string | null + postCode: string | null + address: string | null + addressDetail: string | null + submissionStatus: boolean + submissionDate: string | null } export type SurveyDetailRequest = { - CONTRACT_CAPACITY: string | null - RETAIL_COMPANY: string | null - SUPPLEMENTARY_FACILITIES: string | null // number 배열 - SUPPLEMENTARY_FACILITIES_ETC: string | null - INSTALLATION_SYSTEM: string | null - INSTALLATION_SYSTEM_ETC: string | null - CONSTRUCTION_YEAR: string | null - CONSTRUCTION_YEAR_ETC: string | null - ROOF_MATERIAL: string | null // number 배열 - ROOF_MATERIAL_ETC: string | null - ROOF_SHAPE: string | null - ROOF_SHAPE_ETC: string | null - ROOF_SLOPE: string | null - HOUSE_STRUCTURE: string | null - HOUSE_STRUCTURE_ETC: string | null - RAFTER_MATERIAL: string | null - RAFTER_MATERIAL_ETC: string | null - RAFTER_SIZE: string | null - RAFTER_SIZE_ETC: string | null - RAFTER_PITCH: string | null - RAFTER_PITCH_ETC: string | null - RAFTER_DIRECTION: string | null - OPEN_FIELD_PLATE_KIND: string | null - OPEN_FIELD_PLATE_KIND_ETC: string | null - OPEN_FIELD_PLATE_THICKNESS: string | null - LEAK_TRACE: boolean | null - WATERPROOF_MATERIAL: string | null - WATERPROOF_MATERIAL_ETC: string | null - INSULATION_PRESENCE: string | null - INSULATION_PRESENCE_ETC: string | null - STRUCTURE_ORDER: string | null - STRUCTURE_ORDER_ETC: string | null - INSTALLATION_AVAILABILITY: string | null - INSTALLATION_AVAILABILITY_ETC: string | null - MEMO: string | null + contractCapacity: string | null + retailCompany: string | null + supplementaryFacilities: string | null // number 배열 + supplementaryFacilitiesEtc: string | null + installationSystem: string | null + installationSystemEtc: string | null + constructionYear: string | null + constructionYearEtc: string | null + roofMaterial: string | null // number 배열 + roofMaterialEtc: string | null + roofShape: string | null + roofShapeEtc: string | null + roofSlope: string | null + houseStructure: string | null + houseStructureEtc: string | null + rafterMaterial: string | null + rafterMaterialEtc: string | null + rafterSize: string | null + rafterSizeEtc: string | null + rafterPitch: string | null + rafterPitchEtc: string | null + rafterDirection: string | null + openFieldPlateKind: string | null + openFieldPlateKindEtc: string | null + openFieldPlateThickness: string | null + leakTrace: boolean | null + waterproofMaterial: string | null + waterproofMaterialEtc: string | null + insulationPresence: string | null + insulationPresenceEtc: string | null + structureOrder: string | null + structureOrderEtc: string | null + installationAvailability: string | null + installationAvailabilityEtc: string | null + memo: string | null } export type SurveyDetailCoverRequest = { - DETAIL_INFO: SurveyDetailRequest + detailInfo: SurveyDetailRequest } export type SurveyRegistRequest = { - REPRESENTATIVE: string - STORE: string | null - CONSTRUCTION_POINT: string | null - INVESTIGATION_DATE: string | null - BUILDING_NAME: string | null - CUSTOMER_NAME: string | null - POST_CODE: string | null - ADDRESS: string | null - ADDRESS_DETAIL: string | null - SUBMISSION_STATUS: boolean - SUBMISSION_DATE: string | null - DETAIL_INFO: SurveyDetailRequest | null + representative: string + store: string | null + constructionPoint: string | null + investigationDate: string | null + buildingName: string | null + customerName: string | null + postCode: string | null + address: string | null + addressDetail: string | null + submissionStatus: boolean + submissionDate: string | null + detailInfo: SurveyDetailRequest | null } export type Mode = 'CREATE' | 'EDIT' | 'READ' | 'TEMP' // 등록 | 수정 | 상세 | 임시저장