From f51b03cab742a504d3f076c440e555295e00dcc1 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Tue, 10 Jun 2025 17:13:35 +0900 Subject: [PATCH] docs: add annotations in SurveySale related Files - Added detailed descriptions for search parameters and API functionalities in survey-sales routes. - Improved documentation for inquiry-related hooks and types, enhancing clarity on their usage. - Refactored comments for better readability and consistency across the codebase. --- src/app/api/survey-sales/[id]/route.ts | 172 ++++++++++++- src/app/api/survey-sales/route.ts | 208 ++++++++++----- src/components/inquiry/RegistForm.tsx | 4 + src/components/inquiry/list/ListTable.tsx | 2 + src/components/pdf/SurveySaleDownloadPdf.tsx | 6 +- .../popup/SurveySaleSubmitPopup.tsx | 4 +- .../survey-sale/detail/BasicForm.tsx | 10 +- .../survey-sale/detail/ButtonForm.tsx | 25 +- .../survey-sale/detail/DataTable.tsx | 1 + .../survey-sale/detail/DetailForm.tsx | 4 +- .../survey-sale/detail/RoofForm.tsx | 117 +++++++-- .../survey-sale/list/SearchForm.tsx | 1 + src/hooks/useInquiry.ts | 61 +++++ src/hooks/useSurvey.ts | 107 +++++++- src/store/inquiryFilterStore.ts | 12 + src/store/surveyFilterStore.ts | 39 +++ src/store/surveySaleTabState.ts | 26 -- src/types/Inquiry.ts | 236 ++++++++++++------ src/types/Survey.ts | 167 ++++++++++++- 19 files changed, 999 insertions(+), 203 deletions(-) delete mode 100644 src/store/surveySaleTabState.ts diff --git a/src/app/api/survey-sales/[id]/route.ts b/src/app/api/survey-sales/[id]/route.ts index 5c23cd4..61a7b1c 100644 --- a/src/app/api/survey-sales/[id]/route.ts +++ b/src/app/api/survey-sales/[id]/route.ts @@ -6,6 +6,9 @@ import { sessionOptions } from '@/libs/session' import { cookies } from 'next/headers' import type { SessionData } from '@/types/Auth' +/** + * @description 조사 매물 조회 에러 메시지 + */ const ERROR_MESSAGES = { NOT_FOUND: 'データが見つかりません。', UNAUTHORIZED: 'Unauthorized', @@ -13,14 +16,30 @@ const ERROR_MESSAGES = { FETCH_ERROR: 'データの取得に失敗しました。', } as const -// Role check functions +/** + * @description T01 조회 권한 체크 + * @param {any} survey 조사 매물 데이터 + * @returns {boolean} 조사 매물 임시 저장 여부 + */ const checkT01Role = (survey: any): boolean => survey.SRL_NO !== '一時保存' +/** + * @description Admin (1차 판매점) 권한 체크 + * @param {any} survey 조사 매물 데이터 + * @param {string | null} storeId 판매점 ID + * @returns {boolean} 권한 존재 여부 + */ const checkAdminRole = (survey: any, storeId: string | null): boolean => { if (!storeId) return false return survey.SUBMISSION_STATUS ? survey.SUBMISSION_TARGET_ID === storeId || survey.STORE_ID === storeId : survey.STORE_ID === storeId } +/** + * @description Admin_Sub (2차 판매점) 권한 체크 + * @param {any} survey 조사 매물 데이터 + * @param {string | null} storeId 판매점 ID + * @returns {boolean} 권한 존재 여부 + */ const checkAdminSubRole = (survey: any, storeId: string | null): boolean => { if (!storeId) return false return survey.SUBMISSION_STATUS @@ -28,11 +47,23 @@ const checkAdminSubRole = (survey: any, storeId: string | null): boolean => { : survey.STORE_ID === storeId && !survey.CONSTRUCTION_POINT_ID } +/** + * @description Partner (파트너) 또는 Builder (2차 판매점의 시공권한 회원) 권한 체크 + * @param {any} survey 조사 매물 데이터 + * @param {string | null} builderId 시공점 ID + * @returns {boolean} 권한 존재 여부 + */ const checkPartnerOrBuilderRole = (survey: any, builderId: string | null): boolean => { if (!builderId) return false return survey.CONSTRUCTION_POINT_ID === builderId } +/** + * @description 권한 체크 + * @param {any} survey 조사 매물 데이터 + * @param {any} session 세션 데이터 + * @returns {boolean} 권한 존재 여부 + */ const checkRole = (survey: any, session: any): boolean => { if (!survey || !session.isLoggedIn) return false @@ -47,6 +78,11 @@ const checkRole = (survey: any, session: any): boolean => { return roleChecks[session.role as keyof typeof roleChecks]?.() ?? false } +/** + * @description 조사 매물 조회 + * @param {number} id 조사 매물 ID + * @returns {Promise} 조사 매물 데이터 + */ const fetchSurvey = async (id: number) => { // @ts-ignore return await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({ @@ -55,7 +91,42 @@ const fetchSurvey = async (id: number) => { }) } -// API Route Handlers +/** + * @api {GET} /api/survey-sales/:id 조사 매물 조회 API + * @apiName GET /api/survey-sales/:id + * @apiGroup SurveySales + * @apiDescription 조사 매물 조회 API + * + * @apiParam {Number} id 조사 매물 PRIMARY KEY ID (required) + * @apiParam {Boolean} isPdf pdf 데이터 조회 여부 (optional, default: false) + * + * @apiSuccess {Object} SurveySaleBasicInfo 조사 매물 기본 정보 + * + * @apiError {Number} 401 세션 정보 없음 (로그인 필요) + * @apiError {Number} 403 권한 없음 + * @apiError {Number} 404 조사 매물 없음 + * @apiError {Number} 500 서버 오류 + * + * @apiExample {curl} Example usage: + * curl -X GET \ + * -G "isPdf=true" \ + * http://localhost:3000/api/survey-sales/1 + * + * @apiSuccessExample {json} Success-Response: + * { + * "id": 1, + * "srlNo": "1234567890", + * "storeId": "1234567890", + * "detailInfo": { + * "id": 1, + * "trestleMfpcCd": "1234567890", + * "trestleManufacturerProductName": "1234567890", + * "memo": "1234567890" + * ... + * } + * ... + * } + */ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const cookieStore = await cookies() @@ -69,14 +140,17 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ return NextResponse.json({ error: ERROR_MESSAGES.NOT_FOUND }, { status: 404 }) } + /** pdf 데이터 요청 여부, 권한 여부 확인 */ if (isPdf || checkRole(survey, session)) { return NextResponse.json(survey) } + /** 로그인 여부 확인 */ if (!session?.isLoggedIn || session?.role === null) { return NextResponse.json({ error: ERROR_MESSAGES.UNAUTHORIZED }, { status: 401 }) } + /** 권한 없음 */ return NextResponse.json({ error: ERROR_MESSAGES.NO_PERMISSION }, { status: 403 }) } catch (error) { console.error('Error fetching survey:', error) @@ -84,6 +158,13 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } } +/** + * @description 새로운 SRL_NO 생성 + * @param {string} srlNo 기존 SRL_NO + * @param {string} storeId 판매점 ID + * @param {string} role 권한 + * @returns {Promise} 새로운 SRL_NO + */ const getNewSrlNo = async (srlNo: string, storeId: string, role: string) => { const srlRole = role === 'T01' || role === 'Admin' ? 'HO' : role === 'Admin_Sub' || role === 'Builder' ? 'HM' : '' @@ -113,6 +194,39 @@ const getNewSrlNo = async (srlNo: string, storeId: string, role: string) => { return newSrlNo } +/** + * @api {PUT} /api/survey-sales/:id 조사 매물 수정 API + * @apiName PUT /api/survey-sales/:id + * @apiGroup SurveySales + * @apiDescription 조사 매물 수정 API + * + * @apiParam {Number} id 조사 매물 PRIMARY KEY ID (required) + * @apiBody {Object} survey 조사 매물 데이터 (required) + * @apiBody {Boolean} isTemporary 임시 저장 여부 (optional, default: false) + * @apiBody {String} storeId 판매점 ID (optional) + * @apiBody {String} role 권한 (optional) + * + * @apiSuccess {Object} SurveySaleBasicInfo 수정된 조사 매물 기본 정보 + * + * @apiExample {curl} Example usage: + * curl -X PUT \ + * -H "Content-Type: application/json" \ + * -d '{"survey": {"detailInfo": {"id": 1, "memo": "1234567890", ...}, "srlNo": "1234567890", ...},"storeId": "1234567890", "role": "T01", "isTemporary": false}' \ + * http://localhost:3000/api/survey-sales/1 + * + * @apiSuccessExample {json} Success-Response: + * { + * "id": 1, + * "srlNo": "1234567890", + * "storeId": "1234567890", + * "detailInfo": { + * "id": 1, + * "memo": "1234567890", + * ... + * } + * ... + * } + * */ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params @@ -142,7 +256,24 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ return NextResponse.json({ error: 'データ保存に失敗しました。' }, { status: 500 }) } } - +/** + * @api {DELETE} /api/survey-sales/:id 조사 매물 삭제 API + * @apiName DELETE /api/survey-sales/:id + * @apiGroup SurveySales + * @apiDescription 조사 매물 삭제 API + * + * @apiParam {Number} id 조사 매물 PRIMARY KEY ID (required) + * + * @apiSuccess {String} message 삭제 성공 메시지 + * + * @apiExample {curl} Example usage: + * curl -X DELETE \ + * http://localhost:3000/api/survey-sales/1 + * + * @apiSuccessExample {json} Success-Response: + * { + * "message": "success" + */ export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params @@ -176,6 +307,41 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise } } +/** + * @api {PATCH} /api/survey-sales/:id 조사 매물 제출 API + * @apiName PATCH /api/survey-sales/:id + * @apiGroup SurveySales + * @apiDescription 조사 매물 제출 API + * + * @apiParam {Number} id 조사 매물 PRIMARY KEY ID (required) + * @apiBody {String} targetId 제출 대상 ID (required) + * @apiBody {String} targetNm 제출 대상 이름 (required) + * + * @apiSuccess {String} message 제출 성공 메시지 + * @apiSuccess {Object} data 제출된 조사 매물 기본 정보 + * + * @apiExample {curl} Example usage: + * curl -X PATCH \ + * -H "Content-Type: application/json" \ + * -d '{"targetId": "1234567890", "targetNm": "1234567890"}' \ + * http://localhost:3000/api/survey-sales/1 + * + * @apiSuccessExample {json} Success-Response: + * { + * "message": "success", + * "data": { + * "id": 1, + * "srlNo": "1234567890", + * "storeId": "1234567890", + * "detailInfo": { + * "id": 1, + * "memo": "1234567890", + * ... + * } + * ... + * } + * } + */ export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index dfb486c..c257b05 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -2,17 +2,17 @@ import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' import { convertToSnakeCase } from '@/utils/common-utils' /** - * 검색 파라미터 + * @description 검색 파라미터 타입 */ type SearchParams = { - keyword?: string | null // 검색어 - searchOption?: string | null // 검색 옵션 (select 옵션) - isMySurvey?: string | null // 내가 작성한 매물 - sort?: string | null // 정렬 방식 + keyword?: string | null + searchOption?: string | null + isMySurvey?: string | null + sort?: string | null offset?: string | null - role?: string | null // 회원권한한 - storeId?: string | null // 판매점ID - builderId?: string | null // 시공ID + role?: string | null + storeId?: string | null + builderId?: string | null } type WhereCondition = { @@ -21,35 +21,35 @@ type WhereCondition = { [key: string]: any } -// 검색 가능한 필드 옵션 +/** 검색 가능한 필드 옵션 */ const SEARCH_OPTIONS = [ - 'BUILDING_NAME', // 건물명 - 'REPRESENTATIVE', // 담당자 - 'STORE', // 판매점명 - 'STORE_ID', // 판매점ID - 'CONSTRUCTION_POINT', // 시공점명 - 'CONSTRUCTION_POINT_ID', // 시공점ID - 'CUSTOMER_NAME', // 고객명 - 'POST_CODE', // 우편번호 - 'ADDRESS', // 주소 - 'ADDRESS_DETAIL', // 상세주소 - 'SRL_NO', // 등록번호 + 'BUILDING_NAME', + 'REPRESENTATIVE', + 'STORE', + 'STORE_ID', + 'CONSTRUCTION_POINT', + 'CONSTRUCTION_POINT_ID', + 'CUSTOMER_NAME', + 'POST_CODE', + 'ADDRESS', + 'ADDRESS_DETAIL', + 'SRL_NO', ] as const -// 페이지당 항목 수 +/** 페이지당 기본 항목 수 */ const ITEMS_PER_PAGE = 10 /** - * 키워드 검색 조건 생성 함수 - * @param keyword 검색 키워드 - * @param searchOption 검색 옵션 - * @returns 검색 조건 객체 + * @description 키워드 검색 조건 생성 함수 + * @param {string} keyword 검색 키워드 + * @param {string} searchOption 검색 옵션 + * @returns {WhereCondition} 검색 조건 객체 */ const createKeywordSearchCondition = (keyword: string, searchOption: string): WhereCondition => { const where: WhereCondition = { AND: [] } if (searchOption === 'all') { - // 모든 필드 검색 시 OR 조건 사용 + /** 모든 필드 검색 시 OR 조건 사용 */ where.OR = [] where.OR.push( @@ -58,42 +58,38 @@ const createKeywordSearchCondition = (keyword: string, searchOption: string): Wh })), ) } else if (SEARCH_OPTIONS.includes(searchOption.toUpperCase() as any)) { - // 특정 필드 검색 + /** 특정 필드 검색 */ where[searchOption.toUpperCase()] = { contains: keyword } } return where } /** - * 회원 역할별 검색 조건 생성 함수 - * @param params 검색 파라미터 - * @returns 검색 조건 객체 + * @description 회원 역할별 검색 조건 생성 함수 + * @param {SearchParams} params 검색 파라미터 + * @returns {WhereCondition} 검색 조건 객체 */ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { const where: WhereCondition = { AND: [] } switch (params.role) { - case 'Admin': // 1차점 + case 'Admin': where.OR = [ { - // 같은 판매점에서 작성한 제출/제출되지 않은 매물 AND: [{ STORE_ID: { equals: params.storeId } }], }, { - // MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물 AND: [{ SUBMISSION_TARGET_ID: { equals: params.storeId } }, { SUBMISSION_STATUS: { equals: true } }], }, ] break - case 'Admin_Sub': // 2차점 + case 'Admin_Sub': where.OR = [ { - // MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물 AND: [{ STORE_ID: { equals: params.storeId } }, { CONSTRUCTION_POINT_ID: { equals: params.builderId } }], }, { - // MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물 AND: [ { SUBMISSION_TARGET_ID: { equals: params.storeId } }, { CONSTRUCTION_POINT_ID: { not: null } }, @@ -104,9 +100,8 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { ] break - case 'Builder': // MUSUBI (시공권한 O) - case 'Partner': // PARTNER - // 시공ID 같은 매물 + case 'Builder': + case 'Partner': where.AND?.push({ CONSTRUCTION_POINT_ID: { equals: params.builderId }, }) @@ -129,12 +124,16 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { ] break case 'User': - // 모든 매물 조회 가능 (추가 조건 없음) break } return where } +/** + * @description 권한 별 필수 값 존재 여부 확인, 없을 시 빈 데이터 반환 + * @param {SearchParams} params 검색 파라미터 + * @returns {NextResponse} 세션 체크 결과 + */ const checkSession = (params: SearchParams) => { if (params.role === null) { return NextResponse.json({ data: [], count: 0 }) @@ -152,11 +151,50 @@ const checkSession = (params: SearchParams) => { } /** - * GET 핸들러 - 설문 목록 조회 + * @api {GET} /api/survey-sales 설문 목록 조회 API + * @apiName GET /api/survey-sales + * @apiGroup SurveySales + * @apiDescription 설문 목록 조회 API + * + * @apiParam {String} keyword 검색어 (optional) + * @apiParam {String} searchOption 검색 옵션 (optional) + * @apiParam {String} isMySurvey 내가 작성한 매물 (optional) + * @apiParam {String} sort 정렬 방식 (optional) + * @apiParam {String} offset 페이지 오프셋 (optional) + * @apiParam {String} role 회원권한 (optional) + * @apiParam {String} storeId 판매점ID (optional) + * @apiParam {String} builderId 시공점ID (optional) + * + * @apiSuccess {Object[]} data 설문 목록 데이터 + * @apiSuccess {Number} data.count 설문 목록 개수 + * + * @apiExample {curl} Example usage: + * curl -X GET \ + * -G "keyword=test&searchOption=all&isMySurvey=true&sort=created&offset=0&role=Admin&storeId=1234567890&builderId=1234567890" \ + * http://localhost:3000/api/survey-sales + * + * @apiSuccessExample {json} Success-Response: + * { + * "data": [ + * { + * "id": 1, + * "srlNo": "1234567890", + * "storeId": "1234567890", + * "detailInfo": { + * "id": 1, + * "memo": "1234567890", + * ... + * } + * ... + * } + * ], + * "count": 1 + * } + * */ export async function GET(request: Request) { try { - // URL 파라미터 파싱 + /** URL 파라미터 파싱 */ const { searchParams } = new URL(request.url) const params: SearchParams = { keyword: searchParams.get('keyword'), @@ -169,31 +207,31 @@ export async function GET(request: Request) { builderId: searchParams.get('builderId'), } - // 세션 체크 결과 처리 + /** 세션 체크 결과 처리 */ const sessionCheckResult = checkSession(params) if (sessionCheckResult) { return sessionCheckResult } - // 검색 조건 구성 + /** 검색 조건 구성 */ const where: WhereCondition = { AND: [] } - // 내가 작성한 매물 조건 적용 + /** 내가 작성한 매물 조건 적용 */ if (params.isMySurvey) { where.AND.push({ REPRESENTATIVE_ID: params.isMySurvey }) } - // 키워드 검색 조건 적용 + /** 키워드 검색 조건 적용 */ if (params.keyword && params.searchOption) { where.AND.push(createKeywordSearchCondition(params.keyword, params.searchOption)) } - // 회원 유형 조건 적용 + /** 회원 유형 조건 적용 */ const roleCondition = createMemberRoleCondition(params) if (Object.keys(roleCondition).length > 0) { where.AND.push(roleCondition) } - // 페이지네이션 데이터 조회 + /** 페이지네이션 데이터 조회 */ //@ts-ignore const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ where, @@ -201,7 +239,7 @@ export async function GET(request: Request) { 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 } }) @@ -212,19 +250,41 @@ export async function GET(request: Request) { } /** - * PUT 핸들러 - 상세 정보 추가 + * @api {PUT} /api/survey-sales 설문 상세 정보 추가 API + * @apiName PUT /api/survey-sales + * @apiGroup SurveySales + * @apiDescription 설문 상세 정보 추가 API + * + * @apiParam {Number} id 설문 목록 ID (required) + * @apiBody {Object} detail_info 상세 정보 (required) + * + * @apiSuccess {String} message 성공 메시지 + * + * @apiExample {curl} Example usage: + * curl -X PUT \ + * -H "Content-Type: application/json" \ + * -d '{"id": 1, "detail_info": {"memo": "1234567890"}}' \ + * http://localhost:3000/api/survey-sales + * + * @apiSuccessExample {json} Success-Response: + * { + * "message": "Success Update Survey" + * } + * + * @apiError {Number} 500 서버 오류 */ export async function PUT(request: Request) { try { + /** 요청 바디 파싱 */ const body = await request.json() - // 상세 정보 생성을 위한 데이터 구성 + /** 상세 정보 생성을 위한 데이터 구성 */ const detailInfo = { ...body.detail_info, BASIC_INFO_ID: body.id, } - // 상세 정보 생성 + /** 상세 정보 생성 */ //@ts-ignore await prisma.SD_SURVEY_SALES_DETAIL_INFO.create({ data: detailInfo, @@ -239,6 +299,42 @@ export async function PUT(request: Request) { } } +/** + * @api {POST} /api/survey-sales 설문 상세 정보 추가 API + * @apiName POST /api/survey-sales + * @apiGroup SurveySales + * @apiDescription 설문 상세 정보 추가 API + * + * @apiParam {Object} survey 설문 목록 데이터 (required) + * @apiParam {String} role 회원권한 (required) + * @apiParam {String} storeId 판매점ID (required) + * @returns + * + * @apiSuccess {Object} data 설문 목록 데이터 + * + * @apiExample {curl} Example usage: + * curl -X POST \ + * -H "Content-Type: application/json" \ + * -d '{"survey": {"srlNo": "1234567890", "storeId": "1234567890", "role": "T01", "detail_info": {"memo": "1234567890"}}}' \ + * http://localhost:3000/api/survey-sales + * + * @apiSuccessExample {json} Success-Response: + * { + * "data": { + * "id": 1, + * "srlNo": "1234567890", + * "storeId": "1234567890", + * "detailInfo": { + * "id": 1, + * "memo": "1234567890", + * ... + * } + * ... + * } + * } + * + * @apiError {Number} 500 서버 오류 + */ export async function POST(request: Request) { try { const body = await request.json() @@ -252,8 +348,8 @@ export async function POST(request: Request) { ? '' : null - // 임시 저장 시 임시저장으로 저장 - // 기본 저장 시 (HO/HM) + 판매점ID + yyMMdd + 000 으로 저장 + /** 임시 저장 시 임시저장으로 저장 */ + /** 기본 저장 시 (HO/HM) + 판매점ID + yyMMdd + 000 으로 저장 */ const baseSrlNo = body.survey.srlNo ?? role + @@ -274,10 +370,10 @@ export async function POST(request: Request) { }, }) - // 마지막 번호 추출 + /** 마지막 번호 추출 */ const lastNumber = lastSurvey ? parseInt(lastSurvey.SRL_NO.slice(-3)) : 0 - // 새로운 srlNo 생성 - 임시저장일 경우 '임시저장' 으로 저장 + /** 새로운 srlNo 생성 - 임시저장일 경우 '임시저장' 으로 저장 */ const newSrlNo = baseSrlNo.startsWith('一時保存') ? baseSrlNo : baseSrlNo + (lastNumber + 1).toString().padStart(3, '0') const { detailInfo, ...basicInfo } = body.survey diff --git a/src/components/inquiry/RegistForm.tsx b/src/components/inquiry/RegistForm.tsx index 93df62f..7ad869e 100644 --- a/src/components/inquiry/RegistForm.tsx +++ b/src/components/inquiry/RegistForm.tsx @@ -50,6 +50,7 @@ export default function RegistForm() { const [attachedFiles, setAttachedFiles] = useState([]) + /** 파일 첨부 처리 */ const handleFileChange = (e: React.ChangeEvent) => { const files = e.target.files if (files && files.length > 0) { @@ -58,15 +59,18 @@ export default function RegistForm() { e.target.value = '' } + /** 파일 삭제 처리 */ const handleRemoveFile = (index: number) => { setAttachedFiles(attachedFiles.filter((_, i) => i !== index)) } + /** 필수 필드 포커스 처리 */ const focusOnRequiredField = (fieldId: string) => { const element = document.getElementById(fieldId) if (element) element.focus() } + /** 제출 처리 */ const handleSubmit = async () => { const emptyField = requiredFieldNames.find((field) => inquiryRequest[field.id as keyof InquiryRequest] === '') if (emptyField) { diff --git a/src/components/inquiry/list/ListTable.tsx b/src/components/inquiry/list/ListTable.tsx index 14ffece..b1359f7 100644 --- a/src/components/inquiry/list/ListTable.tsx +++ b/src/components/inquiry/list/ListTable.tsx @@ -57,6 +57,7 @@ export default function ListTable() { } }, [session, inquiryList]) + /** 내 문의 필터 처리 - 체크 시 자신의 문의 목록만 조회 */ const handleMyInquiry = () => { setOffset(1) setInquiryListRequest({ @@ -65,6 +66,7 @@ export default function ListTable() { }) } + /** 답변 여부 필터 처리리 */ const handleFilter = (e: React.ChangeEvent) => { switch (e.target.value) { case 'N': diff --git a/src/components/pdf/SurveySaleDownloadPdf.tsx b/src/components/pdf/SurveySaleDownloadPdf.tsx index 0cda0e1..0030612 100644 --- a/src/components/pdf/SurveySaleDownloadPdf.tsx +++ b/src/components/pdf/SurveySaleDownloadPdf.tsx @@ -20,6 +20,7 @@ export default function SurveySaleDownloadPdf() { const targetRef = useRef(null) const isGeneratedRef = useRef(false) + /** 페이지 랜더링 이후 PDF 생성 */ useEffect(() => { if (isLoadingSurveyDetail || isGeneratedRef.current) return if (surveyDetail === null) { @@ -55,6 +56,7 @@ export default function SurveySaleDownloadPdf() { }, } + /** PDF 생성 이후 세션 여부에 따른 라우팅 처리 */ generatePDF(targetRef, options) .then(() => { setIsShow(false) @@ -75,8 +77,8 @@ export default function SurveySaleDownloadPdf() {
({ ...prev, [field]: value })) } + /** 필수값 검증 */ const validateData = (data: SubmitFormData): boolean => { const requiredFields = FORM_FIELDS.filter((field) => field.required) @@ -110,7 +111,7 @@ export default function SurveySaleSubmitPopup() { return true } - // TODO: Admin_Sub 계정 매핑된 submit target id 추가!!!! && 메일 테스트트 + /** 제출 처리 - 데이터 검증 이후 메일 전송 완료되면 데이터 저장 */ const handleSubmit = () => { if (validateData(submitData)) { window.neoConfirm('送信しますか? 送信後は変更・修正することはできません。', () => { @@ -145,6 +146,7 @@ export default function SurveySaleSubmitPopup() { popupController.setSurveySaleSubmitPopup(false) } + /** 권한 별 폼 필드 렌더링 */ const renderFormField = (field: FormField) => { const isReadOnly = false diff --git a/src/components/survey-sale/detail/BasicForm.tsx b/src/components/survey-sale/detail/BasicForm.tsx index 520393d..fe2b432 100644 --- a/src/components/survey-sale/detail/BasicForm.tsx +++ b/src/components/survey-sale/detail/BasicForm.tsx @@ -1,7 +1,6 @@ 'use client' import { useEffect, useState } from 'react' -import { useSurveySaleTabState } from '@/store/surveySaleTabState' import type { SurveyBasicRequest } from '@/types/Survey' import type { Mode } from 'fs' import { usePopupController } from '@/store/popupController' @@ -16,16 +15,10 @@ interface BasicFormProps { } export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: BasicFormProps) { - const { setBasicInfoSelected } = useSurveySaleTabState() const [isFlip, setIsFlip] = useState(true) const { addressData, resetAddressData } = useAddressStore() const popupController = usePopupController() - useEffect(() => { - setBasicInfoSelected() - }, []) - - // 주소 데이터가 변경될 때만 업데이트 useEffect(() => { if (!addressData) return setBasicInfo({ @@ -59,6 +52,7 @@ export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: Ba onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })} />
+ {/* 페이지 모드 별, 권한 별 판매점, 시공점 입력 여부 처리 */} {mode === 'READ' || session?.role === 'Builder' ? ( <> {storeInput(basicInfo, setBasicInfo, mode)} @@ -148,6 +142,7 @@ export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: Ba ) } +/** 판매점 입력 창 */ const storeInput = (basicInfo: SurveyBasicRequest, setBasicInfo: (basicInfo: SurveyBasicRequest) => void, mode: Mode) => { return (
@@ -163,6 +158,7 @@ const storeInput = (basicInfo: SurveyBasicRequest, setBasicInfo: (basicInfo: Sur ) } +/** 시공점 입력 창 */ const builderInput = (basicInfo: SurveyBasicRequest, setBasicInfo: (basicInfo: SurveyBasicRequest) => void, mode: Mode) => { return (
diff --git a/src/components/survey-sale/detail/ButtonForm.tsx b/src/components/survey-sale/detail/ButtonForm.tsx index d452aec..044a1d4 100644 --- a/src/components/survey-sale/detail/ButtonForm.tsx +++ b/src/components/survey-sale/detail/ButtonForm.tsx @@ -3,7 +3,7 @@ import type { Mode, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' import { useSessionStore } from '@/store/session' import { useEffect, useState } from 'react' -import { useParams, useRouter, useSearchParams } from 'next/navigation' +import { useParams, useRouter } from 'next/navigation' import { requiredFields, useSurvey } from '@/hooks/useSurvey' import { usePopupController } from '@/store/popupController' @@ -62,6 +62,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { }) }, [session, data]) + /** 권한 정리 로직 - 작성자(담당자), 제출 권한자, 제출 수신자*/ const calculatePermissions = (session: any, basicData: SurveyBasicRequest): PermissionState => { const isSubmiter = calculateSubmitPermission(session, basicData) const isWriter = session.userId === basicData.representativeId @@ -70,6 +71,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { return { isSubmiter, isWriter, isReceiver } } + /** 제출 권한 체크 */ const calculateSubmitPermission = (session: any, basicData: SurveyBasicRequest): boolean => { switch (session?.role) { case 'T01': @@ -85,6 +87,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { } } + /** 저장 로직 */ const handleSave = (isTemporary: boolean, isSubmitProcess: boolean) => { const emptyField = validateSurveyDetail(data.roof) const hasEmptyField = emptyField?.trim() !== '' @@ -96,7 +99,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { } } + /** 임시 저장 로직 */ const tempSaveProcess = async () => { + /**route 에 id 가 있는 경우 업데이트, 없는 경우 생성 */ if (!Number.isNaN(id)) { await updateSurvey({ survey: saveData, isTemporary: true }) if (!isUpdatingSurvey) { @@ -115,11 +120,13 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { alert('一時保存されました。') } + /** 입력 필드 포커스 처리 */ const focusInput = (field: keyof SurveyDetailInfo) => { const input = document.getElementById(field) input?.focus() } + /** 저장 로직 */ const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => { if (emptyField?.trim() === '') { await handleSuccessfulSave(isSubmitProcess) @@ -128,7 +135,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { } } + /** 저장 성공 로직 */ const handleSuccessfulSave = async (isSubmitProcess?: boolean) => { + /** route 에 id 가 있는 경우 업데이트, 없는 경우 생성 */ if (!Number.isNaN(id)) { await updateSurvey({ survey: saveData, @@ -139,6 +148,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { setMode('READ') } } else { + /** 제출 로직인 경우 search param 추가 */ const savedId = await createSurvey(saveData) if (isSubmitProcess) { await router.push(`/survey-sale/${savedId}?show=true`) @@ -149,6 +159,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { } } + /** 필수값 미입력 처리 */ const handleFailedSave = (emptyField: string | null) => { if (emptyField?.includes('Unit')) { alert('電気契約容量の単位を入力してください。') @@ -158,6 +169,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { focusInput(emptyField as keyof SurveyDetailInfo) } + /** 삭제 로직 */ const handleDelete = async () => { if (!Number.isNaN(id)) { window.neoConfirm('削除しますか?', async () => { @@ -170,6 +182,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { } } + /** 제출 로직 */ const handleSubmit = async () => { if (data.basic.srlNo?.startsWith('一時保存') && Number.isNaN(id)) { alert('一時保存されたデータは提出できません。') @@ -187,8 +200,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { } } + /** 로그인 여부 체크 */ if (!session?.isLoggedIn) return null + /** 읽기 모드, 제출 된 데이터, 제출 권한자는 리스트 버튼만 표시 */ if (mode === 'READ' && isSubmit && permissions.isSubmiter) { return (
@@ -201,6 +216,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { return ( <> + {/* 읽기 모드 버튼 처리 */} + {/* 작성자 - 수정, 삭제, 제출(미제출인 매물) 버튼 표시 */} + {/* 제출권한자 - 수정, 제출(미제출인 매물) 버튼 표시 */} + {/* 제출수신자 - 수정, 삭제 버튼 표시 */} {mode === 'READ' && (
@@ -212,6 +231,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
)} + {/* 수정, 작성 모드 */} + {/* 작성자 - 임시저장, 저장, 제출(미제출인 매물) 버튼 표시 */} + {/* 제출권한자 - 임시저장, 저장, 제출(미제출인 매물) 버튼 표시 */} + {/* 제출수신자 - 임시저장, 저장 버튼 표시 */} {(mode === 'CREATE' || mode === 'EDIT') && (
diff --git a/src/components/survey-sale/detail/DataTable.tsx b/src/components/survey-sale/detail/DataTable.tsx index bfafecc..38ca7db 100644 --- a/src/components/survey-sale/detail/DataTable.tsx +++ b/src/components/survey-sale/detail/DataTable.tsx @@ -6,6 +6,7 @@ import { SurveyBasicInfo } from '@/types/Survey' export default function DataTable({ surveyDetail }: { surveyDetail: SurveyBasicInfo }) { const router = useRouter() + /** 제출 상태 처리 */ const submitStatus = () => { const { submissionTargetNm, submissionTargetId } = surveyDetail ?? {} diff --git a/src/components/survey-sale/detail/DetailForm.tsx b/src/components/survey-sale/detail/DetailForm.tsx index b4a995a..0f73c46 100644 --- a/src/components/survey-sale/detail/DetailForm.tsx +++ b/src/components/survey-sale/detail/DetailForm.tsx @@ -94,6 +94,7 @@ export default function DetailForm() { })) const [roofInfoData, setRoofInfoData] = useState(roofInfoForm) + /** 제출 팝업 처리 - createSurvey 이후 popup 처리 시 노드 삽입 오류로 인해 별도 처리 */ useEffect(() => { const show = searchParams.get('show') if (show === 'true') { @@ -102,7 +103,7 @@ export default function DetailForm() { } }, [searchParams, pathname]) - // 세션 데이터가 변경될 때 기본 정보 업데이트 + /** 세션 데이터가 변경될 때 기본 정보 업데이트 */ useEffect(() => { if (!session?.isLoggedIn) return setBasicInfoData((prev) => ({ @@ -116,6 +117,7 @@ export default function DetailForm() { })) }, [session?.isLoggedIn]) + /** 조사매물 상세 데이터 업데이트 */ useEffect(() => { if (!isLoadingSurveyDetail && surveyDetail && (mode === 'EDIT' || mode === 'READ')) { const { id, uptDt, regDt, detailInfo, ...rest } = surveyDetail diff --git a/src/components/survey-sale/detail/RoofForm.tsx b/src/components/survey-sale/detail/RoofForm.tsx index f30fc0a..e19d047 100644 --- a/src/components/survey-sale/detail/RoofForm.tsx +++ b/src/components/survey-sale/detail/RoofForm.tsx @@ -19,116 +19,145 @@ type SelectBoxKeys = | 'installationAvailability' export const supplementaryFacilities = [ - { id: 1, name: 'エコキュート' }, //에코큐트 - { id: 2, name: 'エネパーム' }, //에네팜 - { id: 3, name: '蓄電池システム' }, //축전지시스템 - { id: 4, name: '太陽光発電' }, //태양광발전 + /** 에코큐트 */ + { id: 1, name: 'エコキュート' }, + /** 에네팜 */ + { id: 2, name: 'エネパーム' }, + /** 축전지시스템 */ + { id: 3, name: '蓄電池システム' }, + /** 태양광발전 */ + { id: 4, name: '太陽光発電' }, ] export const roofMaterial = [ - { id: 1, name: 'スレート' }, //슬레이트 - { id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글 - { id: 3, name: '瓦' }, //기와 - { id: 4, name: '金属屋根' }, //금속지붕 + /** 슬레이트 */ + { id: 1, name: 'スレート' }, + /** 아스팔트 싱글 */ + { id: 2, name: 'アスファルトシングル' }, + /** 기와 */ + { id: 3, name: '瓦' }, + /** 금속지붕 */ + { id: 4, name: '金属屋根' }, ] export const selectBoxOptions: Record = { installationSystem: [ { + /** 태양광발전 */ id: 1, - name: '太陽光発電', //태양광발전 + name: '太陽光発電', }, { + /** 하이브리드축전지시스템 */ id: 2, - name: 'ハイブリッド蓄電システム', //하이브리드축전지시스템 + name: 'ハイブリッド蓄電システム', }, { + /** 축전지시스템 */ id: 3, - name: '蓄電池システム', //축전지시스템 + name: '蓄電池システム', }, ], constructionYear: [ { + /** 신축 */ id: 1, - name: '新築', //신축 + name: '新築', }, { + /** 기축 */ id: 2, - name: '既築', //기존 + name: '既築', }, ], roofShape: [ { + /** 박공지붕 */ id: 1, - name: '切妻', //박공지붕 + name: '切妻', }, { + /** 기동 */ id: 2, - name: '寄棟', //기동 + name: '寄棟', }, { + /** 한쪽흐름 */ id: 3, - name: '片流れ', //한쪽흐름 + name: '片流れ', }, ], rafterSize: [ { + /** 35mm 이상×48mm 이상 */ id: 1, name: '幅35mm以上×高さ48mm以上', }, { + /** 36mm 이상×46mm 이상 */ id: 2, name: '幅36mm以上×高さ46mm以上', }, { + /** 37mm 이상×43mm 이상 */ id: 3, name: '幅37mm以上×高さ43mm以上', }, { + /** 38mm 이상×40mm 이상 */ id: 4, name: '幅38mm以上×高さ40mm以上', }, ], rafterPitch: [ { + /** 455mm 이하 */ id: 1, name: '455mm以下', }, { + /** 500mm 이하 */ id: 2, name: '500mm以下', }, { + /** 606mm 이하 */ id: 3, name: '606mm以下', }, ], openFieldPlateKind: [ { + /** 구조용합판 */ id: 1, - name: '構造用合板', //구조용합판 + name: '構造用合板', }, { + /** OSB */ id: 2, - name: 'OSB', //OSB + name: 'OSB', }, { + /** 파티클보드 */ id: 3, - name: 'パーティクルボード', //파티클보드 + name: 'パーティクルボード', }, { + /** 소판 */ id: 4, - name: '小幅板', //소판 + name: '小幅板', }, ], installationAvailability: [ { + /** 확인완료 */ id: 1, - name: '確認済み', //확인완료 + name: '確認済み', }, { + /** 미확인 */ id: 2, - name: '未確認', //미확인 + name: '未確認', }, ], } @@ -136,58 +165,69 @@ export const selectBoxOptions: Record = { structureOrder: [ { + /** 지붕재 - 방수재 - 지붕의기초 - 서까래 */ id: 1, - label: '屋根材 > 防水材 > 屋根の基礎 > 垂木', //지붕재 방수재 지붕의기초 서까래 + label: '屋根材 > 防水材 > 屋根の基礎 > 垂木', }, ], houseStructure: [ { + /** 목재 */ id: 1, label: '木製', }, ], rafterMaterial: [ { + /** 목재 */ id: 1, label: '木製', }, { + /** 강재 */ id: 2, label: '強制', }, ], waterproofMaterial: [ { + /** 아스팔트 지붕 940(22kg 이상) */ 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: 'なし', }, @@ -210,6 +250,7 @@ export default function RoofForm(props: { const [isFlip, setIsFlip] = useState(true) const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => { + /** 지붕 경사도, 노지판 두께 처리 - 최대 5자리, 소수점 1자리 처리 */ if (key === 'roofSlope' || key === 'openFieldPlateThickness') { const stringValue = value.toString() if (stringValue.length > 5) { @@ -224,6 +265,7 @@ export default function RoofForm(props: { } } } + /** 전기 계약 용량 처리 - 단위 붙여서 저장*/ if (key === 'contractCapacity') { const remainValue = roofInfo.contractCapacity?.split(' ')[1] ?? roofInfo.contractCapacity if (Number.isNaN(Number(remainValue))) { @@ -235,6 +277,7 @@ export default function RoofForm(props: { setRoofInfo({ ...roofInfo, [key]: value.toString() }) } + /** 전기 계약 용량 단위 처리 */ const handleUnitInput = (value: string) => { const numericValue = roofInfo.contractCapacity?.replace(/[^0-9.]/g, '') || '' setRoofInfo({ @@ -461,6 +504,7 @@ export default function RoofForm(props: { ) } +/** SelectBox 처리 */ const SelectedBox = ({ mode, column, @@ -479,6 +523,7 @@ const SelectedBox = ({ const isSpecialCase = column === 'constructionYear' || column === 'installationAvailability' const showEtcOption = !isSpecialCase + /** SelectBox 값 변경 처리 */ const handleSelectChange = (e: React.ChangeEvent) => { const value = e.target.value const isEtc = value === 'etc' @@ -498,10 +543,16 @@ const SelectedBox = ({ setRoofInfo(updatedData) } + /** 기타 입력 처리 */ const handleEtcInputChange = (e: React.ChangeEvent) => { setRoofInfo({ ...detailInfoData, [`${column}Etc`]: e.target.value }) } + /** Input box 비활성화 처리 + * - 읽기 모드 : 비활성화 + * - 설치 가능 여부 : 기타 입력 창 항상 활성화 + * - 건축 연수 : 신축(1) 체크 시 비활성화 + * */ const isInputDisabled = () => { if (mode === 'READ') return true if (column === 'installationAvailability') return false @@ -552,6 +603,7 @@ const SelectedBox = ({ ) } +/** RadioBox 선택 처리 */ const RadioSelected = ({ mode, column, @@ -572,20 +624,26 @@ const RadioSelected = ({ const isSpecialColumn = column === 'rafterDirection' || column === 'leakTrace' || column === 'insulationPresence' const showEtcOption = !isSpecialColumn + /** RadioBox 값 변경 처리 */ const handleRadioChange = (e: React.ChangeEvent) => { const value = e.target.value + /** 누수 흔적 처리 - boolean 타입이므로 별도 처리 */ if (column === 'leakTrace') { setRoofInfo({ ...detailInfoData, leakTrace: value === '1' }) return } + /** 기타 체크 처리 */ if (value === 'etc') { setEtcChecked(true) setRoofInfo({ ...detailInfoData, [column]: null, [`${column}Etc`]: '' }) return } + /** 단열재 유무 - 있음(1) 선택 시 기타 체크 처리 + * 서까래 방향 - 기타 입력 칸 없음 + * */ const isInsulationPresence = column === 'insulationPresence' const isRafterDirection = column === 'rafterDirection' @@ -598,10 +656,15 @@ const RadioSelected = ({ }) } + /** 기타 입력 처리 */ const handleEtcInputChange = (e: React.ChangeEvent) => { setRoofInfo({ ...detailInfoData, [`${column}Etc`]: e.target.value }) } + /** Input box 비활성화 처리 + * - 읽기 모드 : 비활성화 + * - 단열재 유무 : 단열재 없음(1) 체크 시 비활성화 + * */ const isInputDisabled = () => { if (mode === 'READ') return true if (column === 'insulationPresence') { @@ -657,6 +720,7 @@ const RadioSelected = ({ ) } +/** 다중 선택 처리 */ const MultiCheck = ({ mode, column, @@ -675,6 +739,7 @@ const MultiCheck = ({ const isRoofMaterial = column === 'roofMaterial' const selectedValues = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? '')) + /** 다중 선택 처리 */ const handleCheckbox = (id: number) => { const isOtherSelected = Boolean(etcValue) let newValue: string[] @@ -682,6 +747,7 @@ const MultiCheck = ({ if (selectedValues.includes(String(id))) { newValue = selectedValues.filter((v) => v !== String(id)) } else { + /** 지붕 재료 처리 - 최대 2개 선택 처리 */ if (isRoofMaterial) { const totalSelected = selectedValues.length + (isOtherSelected || isOtherCheck ? 1 : 0) if (totalSelected >= 2) { @@ -694,6 +760,7 @@ const MultiCheck = ({ setRoofInfo({ ...roofInfo, [column]: newValue.join(',') }) } + /** 기타 선택 처리 */ const handleOtherCheckbox = () => { if (isRoofMaterial) { const currentSelected = selectedValues.length @@ -706,17 +773,19 @@ const MultiCheck = ({ const newIsOtherCheck = !isOtherCheck setIsOtherCheck(newIsOtherCheck) - // 기타 선택 해제 시 값도 null로 설정 + /** 기타 선택 해제 시 값도 null로 설정 */ setRoofInfo({ ...roofInfo, [`${column}Etc`]: newIsOtherCheck ? '' : null, }) } + /** 기타 입력 처리 */ const handleOtherInputChange = (e: React.ChangeEvent) => { setRoofInfo({ ...roofInfo, [`${column}Etc`]: e.target.value }) } + /** Input box 비활성화 처리 */ const isInputDisabled = () => { return mode === 'READ' || (!isOtherCheck && !etcValue) } diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 3b910ee..83943a9 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -19,6 +19,7 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string; setKeyword(searchKeyword) setSearchOption(option) } + /** 권한 별 검색 옵션 목록 처리 */ const searchOptions = memberRole === 'Partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS return ( diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index 7bec432..cd32c87 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -6,6 +6,21 @@ import { useMemo } from 'react' import { useSessionStore } from '@/store/session' import { useRouter } from 'next/navigation' +/** + * @description 문의사항 관련 기능을 제공하는 커스텀 훅 + * + * @param {number} [qnoNo] 문의사항 번호 + * @param {string} [compCd] 회사 코드 + * @returns {Object} 문의사항 관련 기능과 데이터 + * @returns {InquiryList[]} inquiryList - 문의사항 목록 + * @returns {boolean} isLoadingInquiryList - 문의사항 목록 로딩 상태 + * @returns {Inquiry|null} inquiryDetail - 문의사항 상세 정보 + * @returns {boolean} isLoadingInquiryDetail - 문의사항 상세 정보 로딩 상태 + * @returns {boolean} isSavingInquiry - 문의사항 저장 중 상태 + * @returns {Function} saveInquiry - 문의사항 저장 함수 + * @returns {Function} downloadFile - 파일 다운로드 함수 + * @returns {CommonCode[]} commonCodeList - 공통 코드 목록 + */ export function useInquiry( qnoNo?: number, compCd?: string, @@ -25,6 +40,12 @@ export function useInquiry( const { axiosInstance } = useAxios() const router = useRouter() + /** + * @description API 에러 처리 및 라우팅 + * + * @param {any} error 에러 객체 + * @returns {void} 라우팅 처리 + */ const errorRouter = (error: any) => { const status = error.response?.status alert(error.response?.data.error) @@ -50,6 +71,13 @@ export function useInquiry( } } + /** + * @description 문의사항 목록 조회 + * + * @returns {Object} 문의사항 목록 데이터 + * @returns {InquiryList[]} data - 문의사항 목록 + * @returns {boolean} isLoading - 문의사항 목록 로딩 상태 + */ const { data: inquiryList, isLoading: isLoadingInquiryList } = useQuery({ queryKey: ['inquiryList', inquiryListRequest, offset], queryFn: async () => { @@ -66,6 +94,12 @@ export function useInquiry( enabled: !!inquiryListRequest, }) + /** + * @description 문의사항 목록 데이터 메모이제이션 + * + * @returns {Object} 메모이제이션된 문의사항 목록 데이터 + * @returns {InquiryList[]} inquiryList - 문의사항 목록 + */ const inquriyListData = useMemo(() => { if (isLoadingInquiryList) { return { inquiryList: [] } @@ -75,6 +109,13 @@ export function useInquiry( } }, [inquiryList, isLoadingInquiryList]) + /** + * @description 문의사항 상세 정보 조회 + * + * @returns {Object} 문의사항 상세 정보 데이터 + * @returns {Inquiry|null} data - 문의사항 상세 정보 + * @returns {boolean} isLoading - 문의사항 상세 정보 로딩 상태 + */ const { data: inquiryDetail, isLoading: isLoadingInquiryDetail } = useQuery({ queryKey: ['inquiryDetail', qnoNo, compCd, session?.userId], queryFn: async () => { @@ -91,6 +132,12 @@ export function useInquiry( enabled: qnoNo !== undefined && compCd !== undefined, }) + /** + * @description 문의사항 저장 + * + * @param {FormData} formData 저장할 문의사항 데이터 + * @returns {Promise} 저장된 문의사항 응답 데이터 + */ const { mutateAsync: saveInquiry, isPending: isSavingInquiry } = useMutation({ mutationFn: async (formData: FormData) => { const resp = await axiosInstance(null).post<{ data: InquirySaveResponse }>('/api/qna/save', formData) @@ -104,6 +151,13 @@ export function useInquiry( }, }) + /** + * @description 파일 다운로드 + * + * @param {number} encodeFileNo 인코딩된 파일 번호 + * @param {string} srcFileNm 원본 파일명 + * @returns {Promise} 다운로드된 파일 데이터 또는 null + */ const downloadFile = async (encodeFileNo: number, srcFileNm: string) => { try { const resp = await fetch(`/api/qna/file?encodeFileNo=${encodeFileNo}&srcFileNm=${srcFileNm}`) @@ -125,6 +179,13 @@ export function useInquiry( } } + /** + * @description 공통 코드 목록 조회 + * + * @returns {Object} 공통 코드 목록 데이터 + * @returns {CommonCode[]} data - 공통 코드 목록 + * @returns {boolean} isLoading - 공통 코드 목록 로딩 상태 + */ const { data: commonCodeList, isLoading: isLoadingCommonCodeList } = useQuery({ queryKey: ['commonCodeList'], queryFn: async () => { diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index bafe8ab..5e50e30 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -6,7 +6,6 @@ import { useSessionStore } from '@/store/session' import { useAxios } from './useAxios' import { queryStringFormatter } from '@/utils/common-utils' import { useRouter } from 'next/navigation' -import { usePopupController } from '@/store/popupController' export const requiredFields = [ { @@ -55,7 +54,24 @@ type ZipCode = { kana2: string kana3: string } - +/** + * @description 조사 매물 관련 기능을 제공하는 커스텀 훅 + * + * @param {number} [id] 조사 매물 ID + * @param {boolean} [isPdf] PDF 뷰 여부 + * @returns {Object} 조사 매물 관련 기능과 데이터 + * @returns {SurveyBasicInfo[]} surveyList - 조사 매물 목록 데이터 + * @returns {SurveyBasicInfo} surveyDetail - 조사 매물 상세 데이터 + * @returns {boolean} isLoadingSurveyList - 조사 매물 목록 로딩 상태 + * @returns {boolean} isLoadingSurveyDetail - 조사 매물 상세 데이터 로딩 상태 + * @returns {boolean} isCreatingSurvey - 조사 매물 생성 중 상태 + * @returns {boolean} isUpdatingSurvey - 조사 매물 수정 중 상태 + * @returns {boolean} isDeletingSurvey - 조사 매물 삭제 중 상태 + * @returns {boolean} isSubmittingSurvey - 조사 매물 제출 중 상태 + * @returns {Function} createSurvey - 조사 매물 생성 함수 + * @returns {Function} updateSurvey - 조사 매물 수정 함수 + * @returns {Function} deleteSurvey - 조사 매물 삭제 함수 + */ export function useSurvey( id?: number, isPdf?: boolean, @@ -84,6 +100,12 @@ export function useSurvey( const { axiosInstance } = useAxios() const router = useRouter() + /** + * @description 조사 매물 목록, 상세 데이터 조회 에러 처리 + * + * @param {any} error 에러 객체 + * @returns {void} 라우팅 처리 + */ const errorRouter = (error: any) => { const status = error.response?.status alert(error.response?.data.error) @@ -109,6 +131,15 @@ export function useSurvey( } } + /** + * @description 조사 매물 목록 조회 + * + * @returns {Object} 조사 매물 목록 데이터 + * @returns {SurveyBasicInfo[]} 조사 매물 목록 데이터 + * @returns {number} 조건에 맞는 조사 매물 총 개수 + * @returns {() => void} 조사 매물 목록 데이터 새로고침 함수 + * @returns {boolean} 조사 매물 목록 로딩 상태 + */ const { data: surveyListData, isLoading: isLoadingSurveyList, @@ -136,6 +167,14 @@ export function useSurvey( } }, }) + + /** + * @description 조사 매물 목록 데이터 메모이제이션 + * + * @returns {Object} 메모이제이션된 조사 매물 목록 데이터 + * @returns {number} count - 조건에 맞는 조사 매물 총 개수 + * @returns {SurveyBasicInfo[]} data - 조사 매물 목록 데이터 + */ const surveyData = useMemo(() => { if (!surveyListData) return { count: 0, data: [] } return { @@ -143,6 +182,14 @@ export function useSurvey( } }, [surveyListData]) + /** + * @description 조사 매물 상세 데이터 조회 + * + * @returns {Object} 조사 매물 상세 데이터 + * @returns {SurveyBasicInfo} surveyDetail - 조사 매물 상세 데이터 + * @returns {boolean} isLoadingSurveyDetail - 조사 매물 상세 데이터 로딩 상태 + * @returns {() => void} refetchSurveyDetail - 조사 매물 상세 데이터 새로고침 함수 + */ const { data: surveyDetail, isLoading: isLoadingSurveyDetail, @@ -166,6 +213,12 @@ export function useSurvey( enabled: id !== 0 && id !== undefined && id !== null, }) + /** + * @description 조사 매물 생성 + * + * @param {SurveyRegistRequest} survey 생성할 조사 매물 데이터 + * @returns {Promise} 생성된 조사 매물 ID + */ const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ mutationFn: async (survey: SurveyRegistRequest) => { const resp = await axiosInstance(null).post<{ id: number }>('/api/survey-sales', { @@ -181,6 +234,16 @@ export function useSurvey( }, }) + /** + * @description 조사 매물 수정 + * + * @param {Object} params 수정할 데이터 + * @param {SurveyRegistRequest} params.survey 수정할 조사 매물 데이터 + * @param {boolean} params.isTemporary 임시 저장 여부 + * @param {string|null} [params.storeId] 판매점 ID + * @returns {Promise} 수정된 조사 매물 데이터 + * @throws {Error} id가 없는 경우 에러 발생 + */ const { mutate: updateSurvey, isPending: isUpdatingSurvey } = useMutation({ mutationFn: async ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string | null }) => { if (id === undefined) throw new Error('id is required') @@ -198,6 +261,16 @@ export function useSurvey( }, }) + /** + * @description 조사 매물 삭제 + * + * @returns {Promise} 삭제 성공 여부 + * @throws {Error} id가 없는 경우 에러 발생 + * + * @example + * // 삭제 성공 시 목록 데이터만 갱신하고, 상세 데이터는 갱신하지 않음 + * // 상세 데이터를 갱신하면 404 에러가 발생할 수 있음 + */ const { mutateAsync: deleteSurvey, isPending: isDeletingSurvey } = useMutation({ mutationFn: async () => { if (id === null) throw new Error('id is required') @@ -212,6 +285,15 @@ export function useSurvey( }, }) + /** + * @description 조사 매물 제출 + * + * @param {Object} params 제출할 데이터 + * @param {string|null} [params.targetId] 제출 대상 ID + * @param {string|null} [params.targetNm] 제출 대상 이름 + * @returns {Promise} 제출 성공 여부 + * @throws {Error} id가 없는 경우 에러 발생 + */ const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({ mutationFn: async ({ targetId, targetNm }: { targetId?: string | null; targetNm?: string | null }) => { if (!id) throw new Error('id is required') @@ -230,6 +312,12 @@ export function useSurvey( }, }) + /** + * @description 조사 매물 상세 데이터 유효성 검사 + * + * @param {SurveyDetailRequest} surveyDetail 검사할 조사 매물 상세 데이터 + * @returns {string} 빈 필드 이름 또는 빈 문자열 + */ const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => { const ETC_FIELDS = ['installationSystem', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const @@ -269,6 +357,13 @@ export function useSurvey( return '' } + /** + * @description 우편번호 검색 + * + * @param {string} zipCode 검색할 우편번호 + * @returns {Promise} 우편번호 검색 결과 + * @throws {Error} 우편번호 검색 실패 시 에러 발생 + */ const getZipCode = async (zipCode: string): Promise => { try { const { data } = await axiosInstance(null).get( @@ -281,6 +376,14 @@ export function useSurvey( } } + /** + * @description 제출 대상 조회 + * + * @param {Object} params 조회할 데이터 + * @param {string} params.storeId 판매점 ID + * @param {string} params.role 사용자 권한 + * @returns {Promise} 제출 대상 목록 + */ const getSubmitTarget = async (params: { storeId: string; role: string }): Promise => { try { if (!params.storeId) { diff --git a/src/store/inquiryFilterStore.ts b/src/store/inquiryFilterStore.ts index b3fb8d9..6a3f676 100644 --- a/src/store/inquiryFilterStore.ts +++ b/src/store/inquiryFilterStore.ts @@ -1,6 +1,9 @@ import { InquiryListRequest } from '@/types/Inquiry' import { create } from 'zustand' +/** + * @description 문의 목록 필터 상태 타입 + */ type InquiryFilterState = { inquiryListRequest: InquiryListRequest setInquiryListRequest: (inquiryListRequest: InquiryListRequest) => void @@ -9,6 +12,15 @@ type InquiryFilterState = { setOffset: (offset: number) => void } +/** + * @description 문의 목록 필터 상태 관리 + * + * @param {InquiryListRequest} inquiryListRequest 문의 목록 요청 파라미터 + * @param {Function} setInquiryListRequest 문의 목록 요청 파라미터 설정 함수 + * @param {Function} reset 문의 목록 요청 파라미터 초기화 함수 + * @param {number} offset 문의 목록 페이지 오프셋 + * @param {Function} setOffset 문의 목록 페이지 오프셋 설정 함수 + */ export const useInquiryFilterStore = create((set) => ({ inquiryListRequest: { compCd: '5200', diff --git a/src/store/surveyFilterStore.ts b/src/store/surveyFilterStore.ts index 1442ad3..1f7860c 100644 --- a/src/store/surveyFilterStore.ts +++ b/src/store/surveyFilterStore.ts @@ -1,5 +1,11 @@ import { create } from 'zustand' +/** + * @description 조사 매물 검색 옵션 목록 + * + * @param {string} id 조사 매물 검색 옵션 ID + * @param {string} label 조사 매물 검색 옵션 라벨 + */ export const SEARCH_OPTIONS = [ { id: 'all', @@ -35,6 +41,12 @@ export const SEARCH_OPTIONS = [ }, ] +/** + * @description 조사 매물 검색 옵션 목록 - 파트너 + * + * @param {string} id 조사 매물 검색 옵션 ID + * @param {string} label 조사 매물 검색 옵션 라벨 + */ export const SEARCH_OPTIONS_PARTNERS = [ { id: 'all', @@ -54,8 +66,19 @@ export const SEARCH_OPTIONS_PARTNERS = [ }, ] +/** + * @description 조사 매물 검색 옵션 목록 타입 정의 + */ export type SEARCH_OPTIONS_ENUM = (typeof SEARCH_OPTIONS)[number]['id'] + +/** + * @description 파트너 권한의 조사 매물 검색 옵션 목록 타입 정의 + */ export type SEARCH_OPTIONS_PARTNERS_ENUM = (typeof SEARCH_OPTIONS_PARTNERS)[number]['id'] + +/** + * @description 조사 매물 정렬 옵션 목록 + */ export type SORT_OPTIONS_ENUM = 'created' | 'updated' type SurveyFilterState = { @@ -72,6 +95,22 @@ type SurveyFilterState = { reset: () => void } +/** + * @description 조사 매물 검색 조건 관리 + * + * @param {string} keyword 검색어 + * @param {SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM} searchOption 검색 옵션 + * @param {string | null} isMySurvey 내 조사 매물 여부 + * @param {SORT_OPTIONS_ENUM} sort 정렬 옵션 + * @param {number} offset 페이지 오프셋 + * + * @param {Function} setKeyword 검색어 설정 함수 + * @param {Function} setSearchOption 검색 옵션 설정 함수 + * @param {Function} setIsMySurvey 내 조사 매물 여부 설정 함수 + * @param {Function} setSort 정렬 옵션 설정 함수 + * @param {Function} setOffset 페이지 오프셋 설정 함수 + * @param {Function} reset 필터 초기화 함수 + */ export const useSurveyFilterStore = create((set) => ({ keyword: '', searchOption: 'all', diff --git a/src/store/surveySaleTabState.ts b/src/store/surveySaleTabState.ts deleted file mode 100644 index f0ab766..0000000 --- a/src/store/surveySaleTabState.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { create } from 'zustand' - -type SurveySaleTabState = { - basicInfoSelected: boolean - roofInfoSelected: boolean - setBasicInfoSelected: () => void - setRoofInfoSelected: () => void - reset: () => void -} - -type InitialState = { - basicInfoSelected: boolean - roofInfoSelected: boolean -} - -const initialState: InitialState = { - basicInfoSelected: true, - roofInfoSelected: false, -} - -export const useSurveySaleTabState = create((set) => ({ - ...initialState, - setBasicInfoSelected: () => set((state) => ({ ...state, basicInfoSelected: true, roofInfoSelected: false })), - setRoofInfoSelected: () => set((state) => ({ ...state, basicInfoSelected: false, roofInfoSelected: true })), - reset: () => set(initialState), -})) diff --git a/src/types/Inquiry.ts b/src/types/Inquiry.ts index 98a98c3..646cce7 100644 --- a/src/types/Inquiry.ts +++ b/src/types/Inquiry.ts @@ -1,94 +1,190 @@ +/** + * @description 문의 목록 요청 파라미터 타입 + */ export type InquiryListRequest = { - compCd: string //company code - langCd: string //language code - storeId: string //store id - siteTpCd: string //site type code (QC: QCast, QR: QRead) - schTitle: string | null //search title - schRegId: string | null //search regId - schFromDt: string | null //search start date - schToDt: string | null //search end date - schAnswerYn: string | null //search answer yn - loginId: string //login id + /* 회사 코드 */ + compCd: string + /* 언어 코드 */ + langCd: string + /* 판매점 ID */ + storeId: string + /* 사이트 유형 코드 */ + siteTpCd: string + /* 검색 제목 */ + schTitle: string | null + /* 검색 등록자 ID */ + schRegId: string | null + /* 검색 시작 일자 */ + schFromDt: string | null + /* 검색 종료 일자 */ + schToDt: string | null + /* 검색 답변 여부 */ + schAnswerYn: string | null + /* 로그인 ID */ + loginId: string } +/** + * @description 문의 목록 응답 타입 + */ export type InquiryList = { - totCnt: number //total count - rowNumber: number //row number - compCd: string //company code - qnaNo: number //qna number - qstTitle: string //title - regDt: string //registration date - regId: string //registration Userid - regNm: string //registration User name - answerYn: string //answer yn - Y / N - attachYn: string | null //attach yn - Y / N - qnaClsLrgCd: string //qna CLS large Code - qnaClsMidCd: string //qna CLS Mid Code - qnaClsSmlCd: string | null //qna CLS Small Code - regUserNm: string //registration User name + /* 총 건수 */ + totCnt: number + /* 행 번호 */ + rowNumber: number + /* 회사 코드 */ + compCd: string + /* 문의 번호 */ + qnaNo: number + /* 문의 제목 */ + qstTitle: string + /* 문의 등록 일자 */ + regDt: string + /* 문의 등록자 이메일 */ + regEmail: string + /* 문의 등록자 ID */ + regId: string + /* 문의 등록자 이름 */ + regNm: string + /* 답변 여부 */ + answerYn: string + /* 첨부 여부 */ + attachYn: string | null + /* 문의 대분류 코드 */ + qnaClsLrgCd: string + /* 문의 중분류 코드 */ + qnaClsMidCd: string + /* 문의 소분류 코드 */ + qnaClsSmlCd: string | null + /* 문의 등록자 이름 */ + regUserNm: string } +/** + * @description 문의 상세 조회 요청 파라미터 타입 + */ export type InquiryDetailRequest = { - compCd: string //company code - langCd: string //language code - qnaNo: number //qna number - loginId: string //login id + /* 회사 코드 */ + compCd: string + /* 언어 코드 */ + langCd: string + /* 문의 번호 */ + qnaNo: number + /* 로그인 ID */ + loginId: string } +/** + * @description 문의 상세 조회 응답 타입 + */ export type Inquiry = { - compCd: string //company code - qnaNo: number //qna number - qstTitle: string //title - qstContents: string //content - regDt: string //registration date - regId: string //registration Userid - regNm: string //registration User name - regEmail: string //registration User email - answerYn: string //answer yn - Y / N - ansContents: string | null //answer content - ansRegDt: string | null //answer registration date - ansRegNm: string | null //answer registration User name - storeId: string | null //store id - storeNm: string | null //store name - regUserNm: string //registration User name - regUserTelNo: string | null //registration User tel number - qnaClsLrgCd: string //qna CLS large Code - qnaClsMidCd: string //qna CLS Mid Code - qnaClsSmlCd: string | null //qna CLS Small Code - listFile: listFile[] | null //Question list file - ansListFile: listFile[] | null //Answer list file + /* 회사 코드 */ + compCd: string + /* 문의 번호 */ + qnaNo: number + /* 문의 제목 */ + qstTitle: string + /* 문의 내용 */ + qstContents: string + /* 문의 등록 일자 */ + regDt: string + /* 문의 등록자 ID */ + regId: string + /* 문의 등록자 이름 */ + regNm: string + /* 문의 등록자 이메일 */ + regEmail: string + /* 답변 여부 */ + answerYn: string + /* 답변 내용 */ + ansContents: string | null + /* 답변 등록 일자 */ + ansRegDt: string | null + /* 답변 등록자 이름 */ + ansRegNm: string | null + /* 판매점 ID */ + storeId: string | null + /* 판매점 이름 */ + storeNm: string | null + /* 문의 등록자 이름 */ + regUserNm: string + /* 문의 등록자 전화번호 */ + regUserTelNo: string | null + /* 문의 대분류 코드 */ + qnaClsLrgCd: string + /* 문의 중분류 코드 */ + qnaClsMidCd: string + /* 문의 소분류 코드 */ + qnaClsSmlCd: string | null + /* 문의 첨부 파일 */ + listFile: listFile[] | null + /* 답변 첨부 파일 */ + ansListFile: listFile[] | null } +/** + * @description 문의 첨부 파일 타입 + */ export type listFile = { - fileNo: number //file number - encodeFileNo: string //encode file number - srcFileNm: string //source file name - fileCours: string //file course - fileSize: number //file size(Byte) - regDt: string //registration date + /* 파일 번호 */ + fileNo: number + /* 인코딩 파일 번호 */ + encodeFileNo: string + /* 소스 파일 이름 */ + srcFileNm: string + /* 파일 코스 */ + fileCours: string + /* 파일 크기 */ + fileSize: number + /* 등록 일자 */ + regDt: string } +/** + * @description 문의 등록 요청 파라미터 타입 + */ export type InquiryRequest = { - compCd: string //company code - siteTpCd: string //site type code(QC: QCast, QR: QRead) - qnaClsLrgCd: string //qna CLS large Code - qnaClsMidCd: string //qna CLS Mid Code - qnaClsSmlCd: string | null //qna CLS Small Code - title: string //title - contents: string //contents - regId: string //registration Userid - storeId: string //store id - regUserNm: string //registration User name - regUserTelNo: string | null //registration User tel number - qstMail: string //mail + /* 회사 코드 */ + compCd: string + /* 사이트 유형 코드 */ + siteTpCd: string + /* 문의 대분류 코드 */ + qnaClsLrgCd: string + /* 문의 중분류 코드 */ + qnaClsMidCd: string + /* 문의 소분류 코드 */ + qnaClsSmlCd: string | null + /* 문의 제목 */ + title: string + /* 문의 내용 */ + contents: string + /* 문의 등록자 ID */ + regId: string + /* 판매점 ID */ + storeId: string + /* 문의 등록자 이름 */ + regUserNm: string + /* 문의 등록자 전화번호 */ + regUserTelNo: string | null + /* 문의 이메일 */ + qstMail: string } +/** + * @description 문의 등록 응답 타입 + */ export type InquirySaveResponse = { - cnt: number | null //count - qnaNo: number //qna number - mailYn: string //mail yn - Y / N + /* 건수 */ + cnt: number | null + /* 문의 번호 */ + qnaNo: number + /* 메일 여부 */ + mailYn: string } +/** + * @description 공통 코드 타입 + */ export type CommonCode = { headCd: string code: string diff --git a/src/types/Survey.ts b/src/types/Survey.ts index 312c96b..d46dae7 100644 --- a/src/types/Survey.ts +++ b/src/types/Survey.ts @@ -1,156 +1,303 @@ +/** + * @description 조사 매물 타입 + */ export type SurveyBasicInfo = { + /* 조사 매물 ID */ id: number + /* 담당자명 */ representative: string + /* 담당자 ID */ representativeId: string | null + /* 판매점명 */ store: string | null + /* 판매점 ID */ storeId: string | null + /* 시공점명 */ constructionPoint: string | null + /* 시공점 ID */ constructionPointId: 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 + /* 제출 대상 판매점 ID */ submissionTargetId: string | null + /* 제출 대상 판매점명 */ submissionTargetNm: string | null - srlNo: string | null //판매점IDyyMMdd000 + /* 일련번호 */ + srlNo: string | null } +/** + * @description 조사 매물 상세 타입 + */ export type SurveyDetailInfo = { + /* 조사 매물 상세 ID */ id: number + /* 조사 매물 기본 데이터 ID */ 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 배열 + /* 지붕재 - 다중 선택 가능 [number]*/ + roofMaterial: string | null + /* 지붕재 기타 */ 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 } +/** + * @description 조사 매물 생성 요청 파라미터 타입 + */ export type SurveyBasicRequest = { + /* 담당자명 */ representative: string + /* 담당자 ID */ representativeId: string | null + /* 판매점명 */ store: string | null + /* 판매점 ID */ storeId: string | null + /* 시공점명 */ constructionPoint: string | null + /* 시공점 ID */ constructionPointId: 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 + /* 제출 대상 판매점 ID */ submissionTargetId: string | null + /* 제출 대상 판매점명 */ submissionTargetNm: string | null - srlNo: string | null //판매점IDyyMMdd000 + /* 일련번호 */ + srlNo: string | null } +/** + * @description 조사 매물 상세 요청 파라미터 타입 + */ export type SurveyDetailRequest = { + /* 전기계약 용량 */ 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 배열 + /* 지붕재 - 다중 선택 가능 [number]*/ + roofMaterial: string | null + /* 지붕재 기타 */ 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 = { - detailInfo: SurveyDetailRequest -} - +/** + * @description 조사 매물 등록 요청 파라미터 타입 + */ export type SurveyRegistRequest = { + /* 담당자명 */ representative: string + /* 담당자 ID */ representativeId: string | null + /* 판매점명 */ store: string | null + /* 판매점 ID */ storeId: 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 + /* 제출 대상 판매점 ID */ submissionTargetId: string | null - srlNo: string | null //판매점IDyyMMdd000 + /* 일련번호 */ + srlNo: string | null } -export type Mode = 'CREATE' | 'EDIT' | 'READ' | 'SUBMIT' // 등록 | 수정 | 상세 | 제출 +/** + * @description 조사 매물 페이지 모드 + */ +export type Mode = 'CREATE' | 'EDIT' | 'READ' | 'SUBMIT' export type SubmitTargetResponse = { + /* 제출 대상 판매점 ID */ targetStoreId: string + /* 제출 대상 판매점명 */ targetStoreNm: string + /* 담당자 ID */ repUserId: string + /* 담당자 이메일 */ repUserEmail: string + /* 권한 */ auth: string }